Съвет за Java 68: Научете как да внедрите командния модел в Java

Моделите на проектиране не само ускоряват фазата на проектиране на обектно-ориентиран (ОО) проект, но също така увеличават производителността на екипа за разработка и качеството на софтуера. А Command модел е модел на поведение обект, който ни позволява да се постигне пълно отделяне между подателя и получателя. ( Изпращачът е обект, който извиква операция, а получателят е обект, който получава заявката за изпълнение на определена операция. С отделянето подателят няма познания за Receiverинтерфейса на.) Терминът заявкатук се отнася до командата, която трябва да бъде изпълнена. Командният модел също ни позволява да варираме кога и как дадена заявка се изпълнява. Следователно, команден модел ни осигурява гъвкавост, както и разширяемост.

В езици за програмиране като C, функционалните указатели се използват за елиминиране на гигантски оператори за превключване. (Вижте "Java Съвет 30: Полиморфизъм и Java" за по-подробно описание.) Тъй като Java няма указатели на функции, можем да използваме шаблона Command, за да реализираме обратно извикване. Ще видите това в действие в първия примерен код по-долу, наречен TestCommand.java.

Разработчиците, свикнали да използват указатели на функции на друг език, може да се изкушат да използват Methodобектите на API за отразяване по същия начин. Например, в статията си "Java Reflection", Пол Трембъл ви показва как да използвате Reflection за реализиране на транзакции, без да използвате оператори за превключване. Устоях на това изкушение, тъй като Sun препоръчва да не се използва Reflection API, когато са достатъчни други инструменти, по-естествени за езика за програмиране Java. (Вижте Ресурси за връзки към статията на Tremblett и за урок на Sun's Reflection.) Вашата програма ще бъде по-лесна за отстраняване на грешки и поддръжка, ако не използвате Methodобекти. Вместо това трябва да дефинирате интерфейс и да го внедрите в класовете, които извършват необходимото действие.

Затова ви предлагам да използвате командния модел, комбиниран с динамичния механизъм за зареждане и свързване на Java, за да приложите указатели на функции. (За подробности относно динамичния механизъм за зареждане и свързване на Java вижте „Джеймс Гослинг и Хенри Макгилтън„ „Езиковата среда на Java - Бяла книга“, изброени в Ресурси.)

Следвайки горното предложение, ние използваме полиморфизма, предоставен от прилагането на шаблон на Command, за да елиминираме гигантски оператори за превключване, което води до разширяеми системи. Също така използваме уникалните механизми за динамично зареждане и свързване на Java, за да изградим динамична и динамично разширяема система. Това е илюстрирано във втория примерен примерен код по-долу, наречен TestTransactionCommand.java.

Моделът Command превръща самата заявка в обект. Този обект може да се съхранява и предава като други обекти. Ключът към този модел е Commandинтерфейс, който декларира интерфейс за изпълнение на операции. В най-простата си форма този интерфейс включва абстрактна executeоперация. Всеки конкретен Commandклас определя двойка действие-приемник, като съхранява Receiverпроменливата като екземпляр. Той осигурява различни реализации на execute()метода за извикване на заявката. The Receiverима необходимите знания за изпълнение на искането.

Фигура 1 по-долу показва Switch- съвкупност от Commandобекти. Той има flipUp()и flipDown()операции в своя интерфейс. Switchсе нарича инвокер, защото извиква операцията за изпълнение в командния интерфейс.

Конкретната команда, LightOnCommandреализира executeработата на командния интерфейс. Той притежава знанията за извикване на работата на съответния Receiverобект. В този случай той действа като адаптер. Под термина адаптер имам предвид, че конкретният Commandобект е обикновен съединител, свързващ Invokerand и Receiverс различни интерфейси.

Клиентът създава обект на Invokerтова Receiver, както и командата бетон обекти.

Фигура 2, диаграмата на последователността, показва взаимодействията между обектите. Тя илюстрира как се Commandотделя Invokerот Receiver(и заявката, която изпълнява). Клиентът създава конкретна команда, като параметризира своя конструктор със съответния Receiver. След това съхранява Commandв Invoker. На Invokerобади конкретната команда, която има познания за извършване на желаната Action()операция.

Клиентът (основната програма в списъка) създава конкретен Commandобект и задава неговия Receiver. Като Invokerобект Switchсъхранява конкретния Commandобект. На Invokerвъпросите на искане, като се обадите executeна Commandобекта. Конкретният Commandобект извиква операции върху него, за Receiverда изпълни заявката.

Ключовата идея тук е, че конкретната команда се регистрира с Invokerи Invokerго извиква обратно, изпълнявайки командата на Receiver.

Примерен код на команден модел

Нека да разгледаме един прост пример, илюстриращ механизма за обратно извикване, постигнат чрез командния модел.

Примерът показва a Fanи a Light. Нашата цел е да разработим устройство, Switchкоето може да включва или изключва обект. Виждаме, че the Fanи the Lightимат различни интерфейси, което означава, че Switchтрябва да е независим от Receiverинтерфейса или той не познава кода> интерфейса на приемника. За да разрешим този проблем, трябва да параметризираме всеки от Switchs със съответната команда. Очевидно Switchсвързаните към Lightще имат различна команда от Switchсвързаните към Fan. За да работи това, Commandкласът трябва да е абстрактен или интерфейс.

Когато конструкторът за a Switchсе извика, той се параметризира със съответния набор от команди. Командите ще се съхраняват като частни променливи на Switch.

Когато flipUp()и flipDown()операциите се наричат, те просто ще направи нужната команда да execute( ). The Switchняма да има представа какво се случва в резултат на execute( )се нарича.

TestCommand.java клас Fan {public void startRotate () {System.out.println ("Вентилаторът се върти"); } public void stopRotate () {System.out.println („Вентилаторът не се върти“); }} клас Light {public void turnOn () {System.out.println ("Light is on"); } public void turnOff () {System.out.println („Светлината е изключена“); }} клас Switch {private Command UpCommand, DownCommand; публичен превключвател (Command Up, Command Down) {UpCommand = Up; // конкретна команда се регистрира с извикващия DownCommand = Down; } void flipUp () {// invoker извиква конкретна команда, която изпълнява командата на приемника UpCommand. изпълни ( ) ; } void flipDown () {DownCommand. изпълни ( ); }} клас LightOnCommand изпълнява Command {private Light myLight; публичен LightOnCommand (Light L) {myLight = L;} public void execute () {myLight. включи( ); }} клас LightOffCommand изпълнява Command {private Light myLight; public LightOffCommand (Light L) {myLight = L; } public void execute () {myLight. изключи( ); }} клас FanOnCommand изпълнява Command {private Fan myFan; публичен FanOnCommand (Fan F) {myFan = F; } public void execute () {myFan. startRotate (); }} клас FanOffCommand изпълнява Command {private Fan myFan; публичен FanOffCommand (Fan F) {myFan = F; } public void execute () {myFan. stopRotate (); }} публичен клас TestCommand {public static void main (String [] args) {Light testLight = new Light (); LightOnCommand testLOC = нов LightOnCommand (testLight); LightOffCommand testLFC = нов LightOffCommand (testLight); Switch testSwitch = нов Switch (testLOC, testLFC); testSwitch.flipUp (); testSwitch.flipDown ();Тест на вентилатора Fan = нов Fan (); FanOnCommand foc = нов FanOnCommand (testFan); FanOffCommand ffc = нов FanOffCommand (testFan); Switch ts = нов Switch (foc, ffc); ts.flipUp (); ts.flipDown (); }} Command.java публичен интерфейс Command {публична абстрактна невалидна изпълнение (); }

Забележете в примера за кода по-горе, че командният модел напълно отделя обекта, който извиква операцията (Switch )- от тези, които имат знанията да я изпълнят - Lightи Fan. Това ни дава голяма гъвкавост: обектът, който издава заявка, трябва да знае само как да я издаде; не е необходимо да знае как ще бъде изпълнено искането.

Команден модел за изпълнение на транзакции

Команден модел е известен също като модел на действие или транзакция. Нека разгледаме сървър, който приема и обработва транзакции, доставени от клиенти чрез TCP / IP сокет връзка. Тези транзакции се състоят от команда, последвана от нула или повече аргументи.

Разработчиците могат да използват инструкция за превключване с случай за всяка команда. Използването на Switchизявления по време на кодиране е признак на лош дизайн по време на фазата на проектиране на обектно-ориентиран проект. Командите представляват обектно-ориентиран начин за подпомагане на транзакции и могат да се използват за решаване на този проблем с дизайна.

В клиентския код на програмата TestTransactionCommand.javaвсички заявки се капсулират в общия TransactionCommandобект. В TransactionCommandконструктора е създадена от клиента и той е регистриран в CommandManager. Заявките на опашка могат да бъдат изпълнени по различно време чрез извикване на runCommands(), което ни дава голяма гъвкавост. Също така ни дава възможност да събираме команди в съставна команда. Аз също имам CommandArgument, CommandReceiverи CommandManagerкласове и подкласове на TransactionCommand- а именно AddCommandи SubtractCommand. Следва описание на всеки от тези класове:

  • CommandArgumentе помощен клас, който съхранява аргументите на командата. Той може да бъде пренаписан, за да опрости задачата за предаване на голям или променлив брой аргументи от всякакъв тип.

  • CommandReceiver изпълнява всички методи за обработка на команди и се изпълнява като модел Singleton.

  • CommandManagerе призоваващият и е Switchеквивалентът от предишния пример. Той съхранява общия TransactionCommandобект в неговата частна myCommandпроменлива. Когато runCommands( )се извика, той извиква execute( )съответния TransactionCommandобект.

В Java е възможно да се търси дефиницията на клас, даден низ, съдържащ името му. В execute ( )работата на TransactionCommandкласа изчислявам името на класа и динамично го свързвам с работещата система - тоест класовете се зареждат в движение, както се изисква. Използвам конвенцията за именуване, име на команда, обединено от низа "Command" като име на подкласа на командата за транзакции, за да може да се зарежда динамично.

Забележете, че Classобектът, върнат от, newInstance( )трябва да бъде предаден на подходящия тип. Това означава, че новият клас трябва или да внедри интерфейс, или да подкласира съществуващ клас, който е известен на програмата по време на компилация. В този случай, тъй като ние прилагаме Commandинтерфейса, това не е проблем.