2012-03-13 4 views
2

Я отметил этот вопрос как Java, так и Scala, потому что, хотя я в основном развивается на Java, я хотел бы узнать, будет ли решение на Scala другим.Проектирование классов с общим интерфейсом, но по-разному.

У меня возникла проблема с проектированием классов для моего приложения. У меня есть набор общих объектов с различным поведением. Когда я недавно прочитал некоторую книгу о шаблонах, я сказал: «Хорошо, я могу использовать шаблон стратегии здесь», определить поведение как некоторый объект поля и делегировать ему всю логику. И вот моя проблема началась :)

Предположим, у меня есть базовый класс Duck, который может летать, и я делегирую полет в какой-нибудь FlyBehaviour.

interface IFlyable { void fly(); } 

interface IFlyBehaviour { void fly(); } 

class Duck implements IFlyable { 
    IFlyBehaviour flyBehaviour; 

    void fly() { 
     flyBehaviour.fly(); 
    } 
} 

Но мои утки немного разные, и я понимаю, что я хочу, чтобы поведение зависело от него. Сначала у меня есть SpaceDuck, который должен летать в космосе и использовать поле spaceShip, которое определяется только для SpaceDuck. Затем у меня есть HelicopterDuck, который я хочу летать как можно ниже и использовать некоторые противовоздушные снаряды, которые определены только для вертолета. Так что в коде это что-то вроде этого

class SpaceDuck extends Duck { 
    String spaceship; 
} 

class SpaceFlyBehaviour implements IFlyBehaviour { 
    void fly() { 
     System.out.println("Flying in space on spaceship: " + spaceduck.spaceship); 
    } 
} 

class HelicopterDuck extends Duck { 
    int flares; 
} 

class HelicopterFlyBehaviour implements IFlyBehaviour { 
    void fly() { 
     while(helicopterduck.flares > 0) { 
      System.out.println("I'm going low and using flares"); 
      helicopterduck.flares--; 
     } 
    } 
} 

в моих реализаций поведения я на самом деле не имеют ссылки на spaceduck или helicopterduck и этот код не будет компилироваться. Я просто представил образец того, как я себе представлял и хотел бы, чтобы это было. Я мог бы изменить IFlyBehaviour и передать утку в качестве аргумента методу fly(), но затем мне нужно сжать, чтобы получить доступ к уткам, которые, по-моему, не очень хорошая идея.

Похоже, что очевидный способ - просто отбросить IFlyBehaviour и переместить логику в метод fly() каждой утки. Но я ожидаю, что многие различные виды космических полетов и поведение вертолетов и fly() не являются единственным методом. Это будут squack(), run() и т. Д., И каждый из них имеет разные настройки поведения. Таким образом, моя иерархия классов станет огромной и неподъемной.

В моем реальном приложении у меня будут некоторые запущенные и остановившиеся экземпляры, которые можно запускать и останавливать по-разному. Один экземпляр будет запущен через SSH-скрипт, другой через MBean (или SSH, это зависит от того, как пользователь настроил его), третий с помощью какой-то третьей стороны и т. Д. Поэтому я надеюсь, что образец Duck хорошо отразит мою проблему.

Любые мысли, чтобы подтолкнуть меня в правильном направлении, были бы очень полезными. Заранее спасибо!

+1

это может вас заинтересовать: http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/ –

+0

Выглядит интересно. Спасибо, что поделился. – Soteric

ответ

2

В Scala, traits были включены только для такого рода материалов.

В Java это сложнее. Я попытался бы избавиться от подклассов утки, а не от поведения, переместив свойства типа в классы поведения. Если вы используете только уток через их общий интерфейс, эти свойства в любом случае недоступны - их следует рассматривать только в момент создания экземпляра.

+0

Это имеет смысл. Я пойду так. Благодарю. – Soteric

2

Во-первых, вы не инициализировали flyBehaviour в классе Duck.Вы можете инициализировать его следующим образом:

class SpaceDuck extends Duck { 
    String spaceship; 
    public Duck() { 
     setFlyBehavior(new SpaceFlyBehaviour(this)); 
    } 
} 

В качестве альтернативы вы можете предоставить справку о утке в методе поведения:

interface IFlyable { void fly(); } 

interface IFlyBehaviour { void fly(IFlyable flyable); } 

Или самый простой способ:

public abstract class Duck { 
    public abstract void fly(); 
} 

public class SpaceDuck extends Duck { 
    String spaceship; 
    public void fly() { 
     System.out.println("Flying in space on spaceship: " + spaceduck.spaceship); 
    } 
} 
1

В Скале вы мог бы сделать что-то вроде этого:

abstract class Duck {def fly: Unit} 

class BasicDuck extends Duck { def fly {println("flying")} } 

trait SpaceshipFlight extends Duck { 
    def spaceship: String 
    abstract override def fly() { 
     super.fly 
     println("but now I'm in space on a: " + spaceship); 
    } 
} 

trait Flares extends SpaceshipFlight { 
    var flares: Int 
    abstract override def fly() { 
     super.fly 
     while(flares > 0) { 
      println("I'm going low: flares are at " + flares) 
      flares=flares-1 
     } 
    } 
} 

На месте вызова можно затем смешать черты вы любите с Duck

scala> new BasicDuck with SpaceshipFlight {def spaceship="rocket"} 
res1: BasicDuck with SpaceshipFlight = [email protected] 

scala> res1.fly 
flying 
but now I'm in space on a: rocket 

//now with flares! 
scala> new BasicDuck with SpaceshipFlight with Flares {def spaceship="rocket"; var flares=5} 
res2: BasicDuck with SpaceshipFlight with Flares = [email protected] 

scala> res2.fly 
flying 
but now I'm in space on a: rocket 
I'm going low: flares are at 5 
I'm going low: flares are at 4 
I'm going low: flares are at 3 
I'm going low: flares are at 2 
I'm going low: flares are at 1 

Следует заметить, что я изменил свой пример немного, чтобы показать, что в Скале вы можете также сделать один признак переопределить поведение из предыдущая, так что вы можете использовать «супер», как мы это делали в чертах SpaceshipFlight и вспышек и сделать черты себя «наращиваемым» (это на самом деле называется «stackable trait pattern»)

EDIT добавление второго пути (где вы можете вводить различные формы поведения после создания экземпляра)

class Duck 
def fly[D <: Duck](duck:D, flyBehavior: (D => Unit)) { 
    flyBehavior(duck) 
}  

class SpaceDuck(val spaceship: String) extends Duck 

val d = new SpaceDuck("rocket") 

// a behavior is now simply a function (no need to wrap it in a class) 
val spacefly = (d: SpaceDuck) => println("flying on a " + d.spaceship) 
val normalfly = (d: SpaceDuck) => println("flying normally. Not using my " + d.spaceship) 

// using different behaviors at runtime 
fly(d, spacefly) // flying on a rocket 
fly(d, normalfly) // flying normally. Not using my rocket 
+0

Черты Scala выглядят великолепно и мощно, но менее гибкими в этом конкретном случае. Я не смогу изменить поведение во время выполнения, правильно? Я также не буду выбирать поведение, используя имя класса, предоставленное пользователем (в Java я могу создать экземпляр объекта по имени класса). Спасибо за образец в любом случае, это было интересно. Я люблю Scala, но мои коллеги не делают :) – Soteric

+1

@Soteric; Я добавил второй способ, которым вы можете закодировать это в scala, что позволяет вам изменять поведение во время выполнения. Что касается создания класса из строки, scala позволяет вам выполнять 'Class.forName (« Duck »). NewInstance', но я согласен с тем, что отражение scala (как и в 2.9) недостаточно мощно. Я читал, что 2.10 должен принести большие улучшения. Также я не пишу это, чтобы убедить вас (или ваших коллег) использовать scala вместо java, я просто пытаюсь ответить на часть scala вашего вопроса :) –

+0

Да, пожалуйста, не поймите меня неправильно. Я просто хотел убрать свои сомнения относительно черт. Я не рассматриваю его как попытку убедить меня в чем-то :) – Soteric

Смежные вопросы