940 likes | 1.16k Views
Паттерн Декоратор. (Decorator). Определение и мотивация Определение Декоратор — структурный шаблон проектирования, предназначенный для динамического подключения дополнительного поведения к объекту. Мотивация. Для динамического и прозрачного для клиентов добавления новых возможностей.
E N D
ПаттернДекоратор (Decorator)
Определениеимотивация Определение Декоратор—структурныйшаблонпроектирования, предназначенныйдлядинамическогоподключения дополнительногоповедениякобъекту. Мотивация Длядинамическогоипрозрачногодляклиентов добавленияновыхвозможностей. Дляреализациивозможностей,которыенужныневсем объектаминевсегда,так,чтобыпотомможнобыло легкоихисключить. Когдарасширениепутемпорожденияподклассовпо каким-топричинамнеудобноилиневозможно.
Участники Component—компонент. Задаетинтерфейсдляобъектов,накоторые впоследствиимогутбытьдинамическивозложены дополнительныеобязанности. ConcreteComponent—конкретныйкомпонент. Определяетобъект,накоторыйвозлагаются дополнительныеобязанности. Decorator—декоратор. ХранитссылкунаобъектComponentинаследует реализациюегоинтерфейсапоумолчанию. ConcreteDecorator—конкретныйдекоратор. Возлагаетдополнительныеобязанностинакомпонент.
Плюсы Большаягибкость,нежелиустатическогонаследования. Паттерндекораторпозволяетболеегибкодобавлять объектуновыеобязанности,чемэтобылобывозможнов случаестатическогонаследования.Декораторможет добавлятьиудалятьэтуфункциональностьвовремя выполненияпрограммы. Крометого,применениенесколькихдекораторовкодному компонентупозволяетпроизвольнымобразомсочетать обязанности.Вчастности,декораторыпозволяютлегко добавитьодноитожесвойстводважды. Позволяетизбежатьперегруженныхфункциямиклассовна верхнихуровняхиерархии. Декораторразрешаетдобавлятьновыеобязанностипомере необходимости.Вместотогочтобыпытатьсяподдержатьвсе мыслимыевозможностиводномсложном,допускающем разностороннююнастройкуклассе,выможетеопределить простойклассипостепеннонаращиватьего функциональностьспомощьюдекораторов.
Пример№1 Классическийпример―добавлениерамокк произвольномуглифу.Рассмотриминтерфейс, которымобладаетглиф: publicinterfaceIGlyph { voidDraw(); }
Пример№1 Определимклассдекоратораглифа,которыйбудет предкомвсехдекораторов: publicabstractclassGlyphDecorator:IGlyph { protectedGlyphDecorator(IGlyphinnerGlyph) { Debug.Assert(innerGlyph!=null); InnerGlyph=innerGlyph; } protectedIGlyphInnerGlyph{get;set;} publicvirtualvoidDraw() { InnerGlyph.Draw(); } }
Пример№1 Определимклассдекоратора,которыйдорисовывает рамку.Такжеондобавляетдополнительноесостояние: толщинуицветрамки: publicclassBorderDecorator:GlyphDecorator{ publicBorderDecorator(IGlyphinnerGlyph, ColorborderColor,intborderWidth):base(innerGlyph){ BorderColor=borderColor; BorderWidth=borderWidth; } publicColorBorderColor{get;set;} publicintBorderWidth{get;set;} publicoverridevoidDraw(){ DrawBorder(); base.Draw(); } }
Пример№1 Длятогочтобынарисоватьрамкукрасногоцветатолщиной водинпиксельвокругизображения,необходимследующий код: IGlyphglyph=newBorderDecorator(newImageGlyph("file"), Color.Red,1); glyph.Draw(); Декораторыможнокомбинироватьпроизвольнымобразом, например,чтобынарисоватьрамкукрасного,апотом черногоцвета,необходимкодтакоговида: IGlyphglyph=newBorderDecorator( newBorderDecorator( newImageGlyph("file"),Color.Red,1), Color.Black,1); glyph.Draw();
Пример№1 Другимпримеромдекоратораглифамогбы бытьдекоратор,которыйпомещаетглифв ограниченнуюобластьидобавляетполосы прокрутки.
Пример№2 Рассмотримиспользованиепаттернана примереоднойизвозможныхреализацийxUnit. Ключевыминтерфейсомбиблиотеки являетсяинтерфейсITestсметодомRun, которыйзапускаетвыполнениетеста. КлассTestCaseпредставляетсобойодин тестовыйметод. Дляопределениянаборатестов используетсяклассTestSuite.
Пример№2 Длясозданиянаборатестовыхметодов необходимосоздатьнаследниккласса TestCaseиопределитьвнѐмтестовые методы. Затемдлякаждоготестовогометодабудет созданэкземплярэтогокласса,который будетдобавленвклассTestSuite. МетодыSetUpиTearDownбудутвыполнены длякаждоготестовогометода.
Пример№2 Нижеприведѐнкодметода,которыйсобираетнабор тестов(TestSuite)извсехтестовыхметодов, определѐнныхвконкретномнаследникаTestCase: publicclassTestCase:ITest { publicstaticITestSuite() { TestSuiteresult=newTestSuite(); foreach(MethodInfomethodinGetTestMethods()) result.AddTest(newTestCase(method)); returnresult; } }
Пример№2 Передвыполнениемвсегонаборатестовнеобходимо выполнитьинициализацию.Создадимдекоратор, которыйбудетвыполнятьнекоторыедействияперед выполнениемвсегонаборатестов. Базовыйклассдлявсехдекораторов: publicclassTestDecorator:ITest { privateITesttest; publicTestDecorator(ITesttest){ this.test=test; } publicvirtualvoidRun(){ test.Run(); } }
Пример№2 Декоратор,которыйдобавляетвыполнениенекоторых действийдоипослевыполнениятеста: publicclassTestSetupDecorator:TestDecorator { publicTestSetupDecorator(ITesttest):base(test){} publicvirtualvoidSetUp(){} publicvirtualvoidTearDown(){} publicoverridevoidRun() { SetUp(); base.Run(); TearDown(); } }
Пример№3 Рассмотримещѐодинпримердекоратора:декоратор событий(EventDecorator).Декораторыданноготипа добавляютвозможностьуведомленияовызоветехили иныхметодов. Пустьестьинтерфейс,определяющийподключениек базеданных: publicinterfaceIConnection { voidConnect(); voidDisconnect(); voidExecuteQuery(stringsql); }
Пример№3 Базовыйклассдлявсехдекораторовбудет следующим: publicclassConnectionDecorator:IConnection{ privateIConnectioninnerConnection; publicConnectionDecorator(IConnectioninnerConnection){ this.innerConnection=innerConnection; } publicvirtualvoidConnect(){ innerConnection.Connect(); } publicvirtualvoidDisconnect(){ innerConnection.Disconnect(); } publicvirtualvoidExecuteQuery(stringsql){ innerConnection.ExecuteQuery(sql); } }
Пример№3 Декораторсобытийдобавляетуведомленияо выполнениитогоилииногометода: publicclassConnectionEventDecorator:ConnectionDecorator { publicConnectionEventDecorator(IConnectioninnerConnection) :base(innerConnection) {} publicEventHandlerBeforeConnect; publicEventHandlerAfterConnect; publicoverridevoidConnect() { if(BeforeConnect!=null) BeforeConnect(this,EventArgs.Empty); base.Connect(); if(AfterConnect!=null) AfterConnect(this,EventArgs.Empty); } }
Пример№4 Примеромдекоратораявляется,например,класс GZipStreamизбиблиотеки.NET: publicclassGZipStream:Stream { publicGZipStream(Streamstream,CompressionModecompressionMode); publicoverrideintRead(byte[]array,intoffset,intcount); }
Пример№4 privatestaticvoidCreateFile(){ Streamstream=newFileStream("text.gzip",FileMode.OpenOrCreate); stream=newGZipStream(stream,CompressionMode.Compress); using(stream){ using(StreamWriterwriter=newStreamWriter(stream)){ writer.WriteLine("BillGates"); writer.WriteLine("LinusTorvalds"); } } } privatestaticvoidReadFile(){ Streamstream=newFileStream("text.gzip",FileMode.Open); stream=newGZipStream(stream,CompressionMode.Decompress); using(stream){ using(StreamReaderreader=newStreamReader(stream)){ while(!reader.EndOfStream) Console.WriteLine(reader.ReadLine()); } } }
Пример№5 Рассмотримследующуюзадачу: необходимовыполнитьпереносданныхиз одногосерверабазданныхвдругой. Приэтомнеобходимообеспечить возможностьвнестиданныесразувбазу данныхи/илисохранитьскриптпереносав файл.
ПаттернКоманда (Command)
Определение Команда—паттернповеденияобъектов, инкапсулирующийзапроскобъектув отдельнуюсущность.
Мотивация Требуетсяпараметризоватьобъектывыполняемымдействием. Впроцедурномязыкетакуюпараметризациюможновыразитьс помощьюфункцииобратноговызова.Командыпредставляют собойобъектно-ориентированнуюальтернативуфункциям обратноговызова. Необходимоопределять,ставитьвочередьивыполнятьзапросыв разноевремя. Необходимообеспечитьвозможностьотменыопераций. ОперацияExecuteобъектаCommandможетсохранитьсостояние, необходимоедляоткатадействий,выполненныхкомандой.Вэтом случаедополнительнаяоперацияUnexecuteотменитдействия, выполненныепредшествующимобращениемкExecute. Требуетсяподдержкапротоколированияизменений. ДополнивинтерфейсклассаCommandоперациямисохраненияи загрузки,можновестипротоколизмененийвовнешнейпамяти. Длявосстановленияпослесбоянужнозагрузитьсохраненные командыиповторновыполнитьих. Необходимоструктурироватьсистемунаосновевысокоуровневых операций,построенныхизпримитивных(транзакции).
Участники Command—команда. Объявляетинтерфейсдлявыполненияопераций. ConcreteCommand—конкретнаякоманда. Определяетсвязьмеждуобъектом-получателемReceiverи действием.РеализуетоперациюExecuteпутемвызова соответствующихоперацийобъектаReceiver. Client—клиент. СоздаетобъектыклассаConcreteCommandиустанавливает ихполучателей Invoker—инициатор. Обращаетсяккомандедлявыполнениязапроса. Receiver—получатель. Располагаетинформациейоспособахвыполнения операций,необходимыхдляудовлетворениязапроса.В ролиполучателяможетвыступатьлюбойкласс.
Плюсы Командаразрываетсвязьмеждуобъектом, инициирующимоперацию,иобъектом,имеющим информациюотом,какеевыполнить. Команды—этосамыенастоящиеобъекты. Допускаетсяманипулироватьимиирасширятьихточно также,каквслучаеслюбымидругимиобъектами: наследовать,компоновать,агрегироватьдляполучения новыхкомандилимодификацииимеющихся. Изпростыхкомандможнособиратьсоставные. Вобщемслучаесоставныекомандыописываются паттерномкомпоновщик. Легкоедобавлениеновыхопераций(команд). Добавлениеновыхкоманднетребуетизменения существующихклассов.
Пример№1 Рассмотримиспользованиеданногопаттернана примерепростоготекстовогоредактора.Пустьунас естьмодельдокумента,котораяобладаетследующим интерфейсом: publicclassDocument { publicstringText{get;set;} publicintCursorPosition{get;set;} publicstringGetSelectedText(); }
Пример№1 Определиминтерфейскомандытакимобразом: publicinterfaceICommand { voidExecute(); }
Пример№1 Командакопированиявбуферобменабудетиметь такойвид publicclassCopyCommand:ICommand { privatereadonlyDocumentdocument; publicCopyCommand(Documentdocument) { this.document=document; } publicvoidExecute() { Clipboard.SetText(document.GetSelectedText()); } }
Пример№1 Командавставкиизбуферабудетвыглядетьтак: publicclassPasteCommand:ICommand { privatereadonlyDocumentdocument; publicPasteCommand(Documentdocument) { this.document=document; } publicvoidExecute() { document.Text.Insert(document.CursorPosition,Clipboard.GetText()); } }
Пример№1 Командасохранениядокументавфайлбудет следующей: publicclassSaveCommand:ICommand { privatereadonlyDocumentdocument; publicSaveCommand(Documentdocument) { this.document=document; } publicvoidExecute() { stringfileName; if(ShowSaveFileDialog(outfileName)) { File.WriteAllText(fileName,document.Text); } } }
Пример№1 Командымогутбытьпривязаныкпунктамменю следующимобразом: publicMenuItemCreateCommandMenuItem(ICommandcommand,stringcaption) { MenuItemresult=newMenuItem(); result.Click+=delegate{command.Execute();}; result.Text=caption; returnresult; } publicvoidBuildContextMenu(ContextMenumenu,Documentdocument) { PasteCommandpasteCommand=newPasteCommand(document); CompileCommandcompileCommand=newCompileCommand(document); menu.MenuItems.Add(CreateCommandMenuItem(pasteCommand,"Paste")); menu.MenuItems.Add(CreateCommandMenuItem(compileCommand,"Compile")); }
Пример№1 Рассмотримтеперькакбудетреализовыватьсяотмена операций. Дляначалапотребуетсядобавитькинтерфейсу командыметодUnExecute.Крометого,некоторые командыисключаютвозможностьотмены.Учтѐмэто, определивметодIsReversible: publicinterfaceICommand { voidExecute(); voidUnExecute(); boolIsReversible{get;} }
Пример№1 Далееопределимочередькоманд: publicclassCommandHistory { publicvoidExecuteCommand(ICommandcommand); publicvoidUndo(); publicvoidRedo(); } ПривызовеметодаExecuteCommandкомандабудет выполнятьсяипомещатьсявсписок.Затемпривызове методаUndoкомандыбудутотменятьсявызовом UnExecuteодназаодной.
Пример№1 Перваяреализацияметодадобавлениябудет следующей: publicclassCommandHistory { privateList<ICommand>commands=newList<ICommand>(); publicvoidExecuteCommand(ICommandcommand) { command.Execute(); commands.Add(command); } }
Пример№1 Дляреализацииметодовотменыиповторанампотребуется понятиетекущейоперации.Заведѐмдляэтогополе CurrentPosition.Методыотменыиповтораоперацийбудут выглядетьследующимобразом: publicclassCommandHistory{ privateList<ICommand>commands=newList<ICommand>(); publicintCurrentPosition{get;privateset;} publicvoidUndo(){ Debug.Assert(CurrentPosition>=0); commands[CurrentPosition].UnExecute(); CurrentPosition--; } publicvoidRedo(){ Debug.Assert(CurrentPosition>=-1); Debug.Assert(CurrentPosition<commands.Count); CurrentPosition++; commands[CurrentPosition].Execute(); } }
Пример№1 Исправимреализациюметодадобавлениякоманды, так,чтобыпридобавленииновойкомандыистория всехотменныхоперацийстиралась: publicclassCommandHistory { privateList<ICommand>commands=newList<ICommand>(); publicintCurrentPosition{get;privateset;} publicvoidExecuteCommand(ICommandcommand) { command.Execute(); for(inti=commands.Count-1;i>CurrentPosition;i--) commands.RemoveAt(i); commands.Add(command); CurrentPosition++; } }
Пример№2 Командытакжемогутбытьвыполненывместе―в однойтранзакции.Дляэтоготребуетсяспециальный типкоманды―составнаякоманда,расширяющая интерфейсметодамидляконструированиякоманды. publicclassCompositeCommand:ICommand{ privatereadonlyList<ICommand>childCommands=newList<ICommand>(); publicCompositeCommand(paramsICommand[]childCommands){ this.childCommands.AddRange(childCommands); } publicvoidAddCommand(ICommandcommand){ childCommands.Add(command); } publicvoidRemoveCommand(ICommandcommand){ childCommands.Remove(command); } }
Пример№2 Реализацияметодоввыполненияиотменыопераций выглядитследующимобразом: publicclassCompositeCommand:ICommand { privatereadonlyList<ICommand>childCommands=newList<ICommand>(); publicvoidExecute() { foreach(ICommandchildCommandinchildCommands) childCommand.Execute(); } publicvoidUnExecute() { foreach(ICommandchildCommandin childCommands.AsEnumerable().Reverse()) childCommand.UnExecute(); } }
Пример№2 Дляобеспеченияатомарностивыполненияоперацииможно использоватьследующуюреализациюметодавыполнения: publicclassCompositeCommand:ICommand{ publicvoidExecute(){ List<ICommand>executedCommands=newList<ICommand>(); foreach(ICommandchildCommandinchildCommands){ try{ childCommand.Execute(); executedCommands.Add(childCommand); } catch{ foreach(ICommandexecutedCommandin executedCommands.AsEnumerable().Reverse()) executedCommand.UnExecute(); break; } } } }
Пример№2 Пустьнашдокументпредставляетсобойнекоторый программныйкод,ипередегокомпиляциейтребуется выполнитьсохранениерезервнойкопиидокумента. Дляэтогоможновоспользоватьсясоставнойкомандой: Documentdocument=newDocument(); CompositeCommandcompositeCommand=newCompositeCommand( newCreateBackupCommand(document), newCompileCommand(document) ); compositeCommand.Execute();
Пример№3 БиблиотекаWPFвсоставе.NETизначально поддерживаеммеханизмкоманд. WindowsPresentationFoundation(WPF)—система построенияграфическихприложенийWindows.Воснове технологиилежитрасширяемыйязыкразметки приложенийXAML—основанныйнаXMLязыкразметки длядекларативногопрограммированияприложений. Болееподробно: http://msdn.microsoft.com/en-us/library/ms752059.aspx http://msdn.microsoft.com/en-us/library/ms754130.aspx
Пример№3 БиблиотекаWPFсодержитследующийинтерфейс: publicinterfaceICommand { voidExecute(objectparameter); boolCanExecute(objectparameter); eventEventHandlerCanExecuteChanged; }
Пример№3 Тогдакомандавставкивыгляделабыследующим образом: publicclassPasteCommand:ICommand{ privatereadonlyDocumentdocument; publicPasteCommand(Documentdocument){ this.document=document; } publicvoidExecute(objectparameter){ document.Text.Insert(document.CursorPosition,Clipboard.GetText()); } publicboolCanExecute(objectparameter){ returnClipboard.ContainsText(); } publiceventEventHandlerCanExecuteChanged; }
Пример№3 Командыможнопривязыватькопределенным элементамуправлениянаформе. НапримерXAMLдлякнопки,выполняющейкоманду сохранения,будеттаким: <ButtonCommand="{BindingPasteCommand}">Paste</Button> Отметим,чтоPasteCommand—этополеобъекта, которыйзаписанвсвойствоDataContextобъемлющего длякнопкиэлемента.
Пример№4 Рассмотримодинспособсозданиякомандбез порождениянаследниковклассаCommandдля каждойотдельнойоперации. Зачастуюоперацииреализуютсяневобъекте команды,авклассе-получателе,которомукоманда лишьделегируетвыполнение. Данныйспособпредусматриваетсоздание специальнойшаблоннойкоманды,которая инициализируетсяобъектом-получателем, указателемнафункцию-член,котораяреализуетту илиинуюоперацию,иаргументамиоперации.