CodeGym /Java блог /Случаен /Шаблон за проектиране на стратегия
John Squirrels
Ниво
San Francisco

Шаблон за проектиране на стратегия

Публикувано в групата
здрасти В днешния урок ще говорим за модел на стратегия. В предишните уроци вече се запознахме накратко с концепцията за наследяване. Ако сте забравor, ще ви напомня, че този термин се отнася за стандартно решение на обичайна програмна задача. В CodeGym често казваме, че можете да търсите в Google отговора на почти всеки въпрос. Това е така, защото вашата задача, Howвато и да е тя, вероятно вече е решена успешно от някой друг. Моделите са изпитани решения на най-често срещаните задачи or методи за решаване на проблемни ситуации. Това са като "колела", които не е нужно да преоткривате сами, но трябва да знаете How и кога да ги използвате :) Друга цел на моделите е да насърчават еднаква архитектура. Четенето на чужд code не е лесна задача! Всеки пише различен code, защото една и съща задача може да се реши по много начини. Но използването на шаблони помага на различни програмисти да разберат логиката на програмиране, без да се задълбочават във всеки ред code (дори когато го виждат за първи път!) Днес разглеждаме един от най-често срещаните шаблони за проектиране, наречен "Стратегия". Дизайн модел: Стратегия - 2Представете си, че пишем програма, която активно ще работи с Conveyance обекти. Няма особено meaning Howво точно прави нашата програма. Създадохме йерархия на класове с един родителски клас Conveyance и три дъщерни класа: Sedan , Truck и F1Car .

public class Conveyance {

   public void go() {
       System.out.println("Moving forward");
   }

   public void stop() {

       System.out.println("Braking!");
   }
}

public class Sedan extends Conveyance {
}

public class Truck extends Conveyance {
}

public class F1Car extends Conveyance {
}
И трите дъщерни класа наследяват два стандартни метода от родителя: go() и stop() . Нашата програма е много проста: колите ни могат да се движат само напред и да натискат спирачките. Продължавайки работата си, решихме да дадем на автомобorте нов метод: fill() (което означава „напълнете резервоара за газ“). Добавихме го към родителския клас Conveyance :

public class Conveyance {

   public void go() {
       System.out.println("Moving forward");
   }

   public void stop() {

       System.out.println("Braking!");
   }
  
   public void fill() {
       System.out.println("Refueling!");
   }
}
Може ли наистина да възникнат проблеми в такава проста ситуация? Всъщност те вече имат... Дизайн модел: Стратегия - 3

public class Stroller extends Conveyance {

   public void fill() {
      
       // Hmm... This is a stroller for children. It doesn't need to be refueled :/
   }
}
В нашата програма вече има превозно средство (бебешка количка), което не се вписва добре в общата концепция. Може да има педали or да е радиоуправляем, но едно е сигурно - няма да има къде да сипе газ. Нашата класова йерархия доведе до наследяване на общи методи от класове, които не се нуждаят от тях. Какво да правим в тази ситуация? Е, можем да заменим метода fill() в класа Stroller , така че нищо да не се случи, когато се опитате да заредите гориво в количката:

public class Stroller extends Conveyance {

   @Override
   public void fill() {
       System.out.println("A stroller cannot be refueled!");
   }
}
Но това едва ли може да се нарече успешно решение, ако не поради дублирания code. Например, повечето от класовете ще използват метода на родителския клас, но останалите ще бъдат принудени да го заменят. Ако имаме 15 класа и трябва да заменим поведението в 5-6 от тях, дублирането на code ще стане доста обширно. Може би интерфейсите могат да ни помогнат? Например така:

public interface Fillable {
  
   public void fill();
}
Ще създадем интерфейс с възможност за попълване с един метод fill() . Тогава тези превозни средства, които трябва да бъдат заредени с гориво, ще внедрят този интерфейс, докато други превозни средства (например нашата бебешка количка) няма. Но този вариант не ни устройва. В бъдеще класовата ни йерархия може да нарасне и да стане много голяма (само си представете колко различни видове превозни средства има в света). Изоставихме предишната version, включваща наследяване, защото не искаме да заменим fill()метод много, много пъти. Сега трябва да го прилагаме във всеки клас! Ами ако имаме 50? И ако ще се правят чести промени в нашата програма (и това почти винаги е вярно за реални програми!), ще трябва да се втурнем през всичките 50 класа и ръчно да променим поведението на всеки от тях. И така, Howво в крайна сметка трябва да направим в тази ситуация? За да решим проблема си, ще изберем различен начин. А именно, ще отделим поведението на нашия клас от самия клас. Какво означава това? Както знаете, всеки обект има състояние (набор от данни) и поведение (набор от методи). Поведението на нашия клас за пренасяне се състои от три метода: go() , stop() и fill() . Първите два метода са добри, Howвито са. Но ние ще преместим третия метод отКлас на транспортиране . Това ще отдели поведението от класа (по-точно, ще отдели само част от поведението, тъй като първите два метода ще останат там, където са). И така, къде трябва да поставим метода fill() ? Нищо не ми идва на ум :/ Изглежда, че е точно там, където трябва да бъде. Ще го преместим в отделен интерфейс: FillStrategy !

public interface FillStrategy {

   public void fill();
}
Защо се нуждаем от такъв интерфейс? Всичко е просто. Сега можем да създадем няколко класа, които имплементират този интерфейс:

public class HybridFillStrategy implements FillStrategy {
  
   @Override
   public void fill() {
       System.out.println("Refuel with gas or electricity — your choice!");
   }
}

public class F1PitstopStrategy implements FillStrategy {
  
   @Override
   public void fill() {
       System.out.println("Refuel with gas only after all other pit stop procedures are complete!");
   }
}

public class StandardFillStrategy implements FillStrategy {
   @Override
   public void fill() {
       System.out.println("Just refuel with gas!");
   }
}
Създадохме три поведенчески стратегии: една за обикновени автомобor, една за хибриди и една за състезателни автомобor от Формула 1. Всяка стратегия прилага различен алгоритъм за зареждане с гориво. В нашия случай ние просто показваме низ на конзолата, но всеки метод може да съдържа няHowва сложна логика. Какво ще правим след това?

public class Conveyance {

   FillStrategy fillStrategy;

   public void fill() {
       fillStrategy.fill();
   }

   public void go() {
       System.out.println("Moving forward");
   }

   public void stop() {
       System.out.println("Braking!");
   }
  
}
Използваме нашия интерфейс FillStrategy като поле в родителския клас Conveyance . Обърнете внимание, че не посочваме конкретна реализация — използваме интерфейс. Класовете автомобor ще се нуждаят от специфични реализации на интерфейса FillStrategy :

public class F1Car extends Conveyance {

   public F1Car() {
       this.fillStrategy = new F1PitstopStrategy();
   }
}

public class HybridCar extends Conveyance {

   public HybridCar() {
       this.fillStrategy = new HybridFillStrategy();
   }
}

public class Sedan extends Conveyance {

   public Sedan() {
       this.fillStrategy = new StandardFillStrategy();
   }
}

Нека да видим Howво имаме!

public class Main {

   public static void main(String[] args) {

       Conveyance sedan = new Sedan();
       Conveyance hybrid = new HybridCar();
       Conveyance f1car = new F1Car();

       sedan.fill();
       hybrid.fill();
       f1car.fill();
   }
}
Конзолен изход:

Just refuel with gas! 
Refuel with gas or electricity — your choice! 
Refuel with gas only after all other pit stop procedures are complete!
Страхотен! Процесът на зареждане с гориво работи Howто трябва! Между другото, нищо не ни пречи да използваме стратегията като параметър в конструктора! Например така:

public class Conveyance {

   private FillStrategy fillStrategy;

   public Conveyance(FillStrategy fillStrategy) {
       this.fillStrategy = fillStrategy;
   }

   public void fill() {
       this.fillStrategy.fill();
   }

   public void go() {
       System.out.println("Moving forward");
   }

   public void stop() {
       System.out.println("Braking!");
   }
}

public class Sedan extends Conveyance {

   public Sedan() {
       super(new StandardFillStrategy());
   }
}



public class HybridCar extends Conveyance {

   public HybridCar() {
       super(new HybridFillStrategy());
   }
}

public class F1Car extends Conveyance {

   public F1Car() {
       super(new F1PitstopStrategy());
   }
}
Нека изпълним нашия метод main() (който остава непроменен). Получаваме същия резултат! Конзолен изход:

Just refuel with gas! 
Refuel with gas or electricity — your choice! 
Refuel with gas only after all other pit stop procedures are complete!
Шаблонът за проектиране на стратегия дефинира семейство от алгоритми, капсулира всеки от тях и гарантира, че те са взаимозаменяеми. Позволява ви да модифицирате алгоритмите, независимо от това How се използват от клиента (това определение, взето от книгата „Head First Design Patterns“, ми се струва отлично). Модел на проектиране: Стратегия - 4Вече посочихме семейството от алгоритми, които ни интересуват (начини за зареждане на автомобor) в отделни интерфейси с различни реализации. Отделихме ги от самата кола. Сега, ако трябва да направим няHowви промени в конкретен алгоритъм за зареждане с гориво, това няма да повлияе по ниHowъв начин на нашите класове автомобor. И за да постигнем взаимозаменяемост, просто трябва да добавим един метод за настройка към нашия клас Conveyance :

public class Conveyance {

   FillStrategy fillStrategy;

   public void fill() {
       fillStrategy.fill();
   }

   public void go() {
       System.out.println("Moving forward");
   }

   public void stop() {
       System.out.println("Braking!");
   }

   public void setFillStrategy(FillStrategy fillStrategy) {
       this.fillStrategy = fillStrategy;
   }
}
Сега можем да променяме стратегиите в движение:

public class Main {

   public static void main(String[] args) {

       Stroller stroller= new Stroller();
       stroller.setFillStrategy(new StandardFillStrategy());

       stroller.fill();
   }
}
Ако бебешките колички изведнъж започнат да работят на бензин, нашата програма ще бъде готова да се справи с този сценарий :) И това е всичко! Научихте още един дизайнерски шаблон, който несъмнено ще бъде от съществено meaning и полезен при работа по реални проекти :) До следващия път!
Коментари
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION