Знакомство с паттернами
Стоимость тура с проездом на АВТОБУСЕ
* - трансфер для туристов из Костромы до Ярославля и обратно – за доп. плату 1100 р. /чел. - трансфер для туристов из Иваново до Ярославля и обратно – за доп. плату 1500 р./чел. - трансфер для туристов из Данилова до Ярославля и обратно - за доп. плату 1100 р./чел. - трансфер для туристов из Вологды до Ярославля и обратно - за доп. плату 2000 р./чел. - трансфер для туристов из Углича до Ярославля и обратно – за доп. плату 1500 р./чел. **- возможна замена проезда на автобусе в одну сторону на проезд поездом. Стоимость тура с проездом на ПОЕЗДЕ
Стоимость тура БЕЗ ПРОЕЗДА
Введение в паттерны проектирования Java Введение Данная работа не претендует на полное освещение паттернов проектирования в java. Целью работы является демонстрация применения паттернов для тех, кто впервые столкнулся с проблемами объектно-ориентированного проектирования. Паттерны - это архитектурная конструкция, помогающая описать и решить некую задачу проектирования. Ко времени представления паттернов разработка программного обеспечения(ПО) стала индустриальной задачей. Многие программисты понимали, что не стоит изобретать велосипед при создании нового ПО. Использование паттернов часто помогает решить эту задачу и бывает полезным, как отдельному разработчику, так и целой команде. Кэнт Бэк и Вард Каннингем представили первые шаблоны проектирования для языка Smalltalk в 1987 г. Э. Гамма, Р. Хелм, Р. Джонсон и Дж. Влиссидес в 1995 г опубликовали книгу «Приемы объектно–ориентированного проектирования. Паттерны проектирования», в которой были представлены 23 шаблона, ставших сейчас основными. С этой книги началось признание паттернов в мире программирования. Издание «банды четырех» (так в шутку прозвали авторов книги) до сих пор остается одним из ИТ-бестселлеров. «Банда четырех» разделила паттерны проектирования на три основные группы: · порождающие — призванные создавать объекты; · структурные — меняющие структуру взаимодействия между классами; · поведенческие — отвечающие за поведение объектов.
Знакомство с паттернами Для знакомства с паттернами, выберем для обсуждения какой-нибудь паттерн, например паттерн «стратегия». Это паттерн поведения объектов, инкапсулирующий отдельные алгоритмы. Попробуем разобраться в старом вопросе - что лучше отношение «содержит» или отношение «является». Иными словами - что применять наследование и композицию. Разберемся в терминологии. Если между объектами существует отношение «является», то каждый объект подкласса является объектом суперкласса. В языке java объектные переменные являются полиморфными, т.е. объект подкласса можно присвоить переменной суперкласса. Автоматический замещение(перегрузка) метода суперкласса методом подкласса во время выполнения программы называется динамическим связыванием (аналог – виртуальный метод с++). Рассмотрим традиционный пример с использованием полиморфизма. Допустим, имеется абстрактный класс Animmal, содержащий абстрактный методы speak(), sleep() и eat(). Имеется также две конкретных реализации класса Animmal – Dog и Cat. Создадим домашний зоопарк, состоящий из собаки и кошки. Вследствие динамического связывания животные домашнего зоопарка разговаривают по-своему - собаки лают, а кошки мяукают. package homeStrategy;
public class home { public static void main(String [] agr){ animmal []homeZoo = new animmal[2]; homeZoo[0]=new Dog(2,"Fido"); homeZoo[1]= new Cat(5,"Tom"); for(animmal x:homeZoo) x.speak(); } } abstract class animmal{ int age; String name; animmal(){} animmal(int age, String name){ this.age=age; this.name = name; } abstract void speak(); abstract void sleep(); abstract void eat(); } class Dog extends animmal{ Dog(int age,String name){ super(age,name); } @Override void speak() { System.out.println("Woof"); }
@Override void sleep() { System.out.println("I can sleap"); }
@Override void eat() { System.out.println("I can eate sugar"); }
} class Cat extends animmal{ Cat(int age,String name){ super(age,name); } @Override void speak() { System.out.println("Meow"); }
@Override void sleep() { System.out.println("I can sleap"); }
@Override void eat() { System.out.println("I cannot eat sugar"); }
} Допустим ситуация в доме изменилась – появился ребенок, у которого появились игрушечные собаки – плюшевые(PlushDog), резиновые(RubberDog), деревянные(WoodenDog). В связи с таким разнообразием необходимо добавить в класс Dog методы – плавать(swim) и двигаться(move), а также изменить метод speak(). Для простоты примера не будем в дальнейшем рассматривать класс Cat, откажемся от класса Animmal, заменим его классом Dog и добавим классы PlushDog, RubberDog и WoodenDog. package DogHome;
public class Dog { int age; String name; Dog(){} Dog(int age, String name){ this.age=age; this.name = name; } void speak() { System.out.println("Woof"); }
void sleep() { System.out.println("I can sleap"); }
void eat() { System.out.println("I can eat sugar"); }
} class PlushDog extends Dog{ PlushDog(int age, String name){ super(age,name); } @Override void eat() { System.out.println("I cannot eat"); } } class RubberDog extends Dog{ RubberDog(int age, String name){ super(age,name); } @Override void speak() { System.out.println("Peep"); } @Override void sleep() { System.out.println("I can sleap"); } @Override void eat() { System.out.println("I cannot eat"); } } class WoodenDog extends Dog{ WoodenDog(int age, String name){ super(age,name); } @Override void speak() { System.out.println("I can't speak"); } @Override void sleep() { System.out.println("I can't sleap"); } @Override void eat() { System.out.println("I can't eat"); } } public class Home { public static void main(String [] arg){ Dog []DogRoom = new Dog[4]; DogRoom[0] = new Dog(2,"Fido"); DogRoom[1] = new PlushDog(2,"Sharik"); DogRoom[2] = new RubberDog(2,"Bobik"); DogRoom[3] = new WoodenDog(2,"Dik"); for(Dog x:DogRoom){ x.speak(); }
} } При таком определении классов ничего не сказано о методах плавать и двигаться. Будем считать, что деревянная собака недвижима и не может плавать, резиновая и настоящая могут плавать, плюшевая не может плавать, но может двигаться. Возникает проблема с методами swim и move. Можно добавить эти методы в класс Dog и перегрузить их для каждой собаки. Реализовать метод плавать и двигаться должны не все субклассы класса Dog. Всегда можно реализовать в субклассе метод, который ничего не делает, например void swim(){}. Но, тогда при появлении нового вида собаки, нужно будет всегда перегружать метод swim(). Что мы и сделали с методами speak(), sleep() и eat() при определении игрушечных собак. При дальнейшем рассмотрении класса Dog откажемся от методов sleep() и eat(). И покажем как более простым способом заставить только некоторых собак плавать и двигаться. Давайте поместим методы swim и move в интерфейсы Swimable и Moveable и соответствующие собаки будут наследовать эти интерфейсы. Например, public interface MoveAble { void move(); } class PlushDog extends Dog implements MoveAble{ PlushDog(int age, String name){ super(age,name); } @Override void eat() { System.out.println("I cannot eat"); }
@Override public void move() { System.out.println("I can move"); } } Реализация такого интерфейса частично решает проблему, но исключает возможность повторного использования кода (интерфейс не имеет реализации). Итак, наследование не подходит, использование интерфейсов тоже. Можно для решения проблемы воспользоваться следующим правилом. Переменные составляющие объекта необходимо выделить и инкапсулировать, так чтобы позднее их было можно изменять, не воздействуя на постоянные составляющие. Это правило общее для всех приемов проектирования. Все паттерны обеспечивают возможность изменения некоторой части системы, независимо от других частей. Создадим новые классы для представления аспектов swim и move, так называемые классы поведения. package DogHome; public interface MoveBehavior { public void move(); } package DogHome; public interface SwimBehavior{ public void swim(); }
Классы реализующие поведение (либо плавает, либо нет, либо двигается, либо нет). package DogHome; public class Swim implements SwimBehavior{
@Override public void swim() { System.out.println("I can swim"); }
package DogHome; public class NoSwim implements SwimBehavior{
@Override public void swim() { System.out.println("I can’t swim"); }
}
package DogHome; public class Move implements MoveBehavior{
@Override public void move() { System.out.println("I can move"); }
} package DogHome; public class NoMove implements MoveBehavior{
@Override public void move() { System.out.println("I can’t move"); }
}
Тогда в классе Dog можно создать два объекта, отвечающих за поведение swim и move public class Dog { int age; String name; MoveBehavior moveBehavior; SwimBehavior swimBehavior; Dog(){} Dog(int age, String name){ this.age=age; this.name = name; moveBehavior = new Move(); swimBehavior = new Swim();
} void speak() { System.out.println("Woof"); }
//делегируем операции классам поведения void swim(){ swimBehavior.swim(); } void move(){ moveBehavior.move(); }
} В конструкторах классов игрушечных собак, создадим экземпляры соответствующих классов поведения: class PlushDog extends Dog { PlushDog(int age, String name){ this.age=age; this.name = name; moveBehavior = new Move(); swimBehavior = new NoSwim(); }
}
class RubberDog extends Dog{ RubberDog(int age, String name){ this.age=age; this.name = name; moveBehavior = new NoMove(); swimBehavior = new Swim(); } @Override void speak() { System.out.println("Peep"); } }
class WoodenDog extends Dog{ WoodenDog(int age, String name){ this.age=age; this.name = name; moveBehavior = new NoMove(); swimBehavior = new NoSwim(); } @Override void speak() { System.out.println("I can't speak"); } } После всего сделанного легко выяснить, что могут сделать собаки, находящиеся в комнате. package DogHome;
public class Home { public static void main(String [] arg){ Dog []DogRoom = new Dog[4]; DogRoom[0] = new Dog(2,"Fido"); DogRoom[1] = new PlushDog(2,"Sharik"); DogRoom[2] = new RubberDog(2,"Bobik"); DogRoom[3] = new WoodenDog(2,"Dik"); for(Dog x:DogRoom){ x.speak(); x.swim(); x.move(); }
} } Собаки последовательно демонстрируют свои способности. Woof I can swim I can move Woof I can't swim I can move Peep I can swim I can't move I can't speak I can't swim I can't move
Получилось, что класс Dog содержит экземпляры MoveBehavior и SwimBehavior, которым делегируется выполнение соответствующих действий. Это и есть механизм композиции. Системы, созданные на основе композиции, обладают большей гибкостью. Они позволяют изменять поведение объекта во время выполнения. Покажем, как это сделать. Добавим к класс Dog методы void setMoveBehavior(MoveBehavior move){ moveBehavior = move; } void setSwimBehavior(SwimBehavior swim){ swimBehavior = swim; } После этого можно изменить поведение резиновой собаки (RubberDog). Допустим, что эта собака может плавать только в ванне. Создадим класс package DogHome; public class SwimInBath implements SwimBehavior{
@Override public void swim() { System.out.println("I can swim in the bath"); }
} и изменим поведение резиновой собаки, находящейся в комнате DogRoom[2].setSwimBehavior(new SwimInBath()); Теперь наши комнатные собаки могут просто плавать, плавать в ванне или вообще не плавать. Описанный выше прием проектирования, в котором поведение делегируется объекту внутри класса, называется паттерн «стратегия». Заметим различие между методами speak и swim. Способ плавания можно изменить в процессе выполнения программы, а способ разговора – нет. Для изменения способа разговора надо изменять метод speak в классах «игрушечная собака». Это происходит из-за того, что метод разговора не делегирован объекту внутри класса.
|