Разработка классов с общим интерфейсом, но другим поведением - PullRequest
2 голосов
/ 13 марта 2012

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

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

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

interface IFlyable { void fly(); }

interface IFlyBehaviour { void fly(); }

class Duck implements IFlyable {
    IFlyBehaviour flyBehaviour;

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

Но мои утки немного отличаются, и я понимаю, что хочу, чтобы поведение зависело от этого. Сначала у меня есть SpaceDuck, который должен летать в космосе и использовать поле spaceShip, которое определено только для SpaceDuck. Затем у меня есть HelicopterDuck, который я хочу летать как можно ниже и использую некоторые противовоздушные ракеты, которые определены только для 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 и передать duck в качестве аргумента метода fly (), но затем мне пришлось понизить рейтинг, чтобы получить доступ к определенным полям duck, что, я думаю, не очень хорошая идея.

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

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

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

Ответы [ 3 ]

2 голосов
/ 13 марта 2012

Во-первых, вы не инициализировали 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);
    }
}
2 голосов
/ 13 марта 2012

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

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

1 голос
/ 13 марта 2012

В scala вы можете сделать что-то вроде этого:

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
        }
    }
}

На сайте вызовов вы можете затем смешать понравившиеся вам черты с вашей уткой

scala> new BasicDuck with SpaceshipFlight {def spaceship="rocket"}
res1: BasicDuck with SpaceshipFlight = $anon$1@751d3ec8

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 = $anon$1@4f6b3939

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

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

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...