잊지 않겠습니다.

Structural Pattern

Book 2009. 1. 7. 11:37

Structual Pattern은 system이 design이 될때, 또는 유지보수 또는 확장시에 고려되게 된다. 

특히 구버젼 시스템에 대한 확장시에 특히 유용하게 된다.  새로운 요구사항이 들어왔을 때에, 기존의 Class를 변경하는 것이 아닌, Decorator pattern을 이용해서 기존의 class를 유지하는 형태로 구현되게 된다. 

 

  • 존재하는 객체에 대해서 빈번하게 새로운 작업이 행해지게 될때,(decorator)
  • 객체에 대한 동작을 행할때, (proxy)
  • 요구시에 비용이 많이 드는 객체를 생성할때, (proxy)
  • interface의 개발과 process에 의존적인 component를 구현할때, (bridge)
  • 서로 양립되지 않는 interface를 서로간에 일치시킬 때 (adapter)
  • 작고 많은 객체들의 비용을 줄일때 (flyweight)
  • 하나의 entry point를 갖는 여러개의 subsystem을 새롭게 개편시킬 때 (Facade)
  • 실행시에 객체를 선택 또는 변경시킬때 (Bridge)
  • 복잡한 subsystem을 간략화 시킬때 (Facade)
  • single objects와 composite object를 하나의 방법으로 다룰때(Composite) 

 

Decorator Pattern

Role

새로운 상태나 동작을 객체에 주기 위한 Pattern.  구현에서 가장 생각해야지 될 내용은 원 class의 상속과 class의 instance 생성을 포함한다는 것이다.

 

Illustration

Decorator pattern은 존재하는 object에 새롭게 더하는 것이 된다.

원 객체는 decorations에 대해서 알지 못하고, 그것에 대하여 관여하지 않는다.

모든 객체는 모든 option을 가지지 않고, 큰 일을 하나가 도맡아서 하지 않는다.

모든 decorations는 서로간에 연관되게 된다.

docorations는 서로간에 결합될 수 있고, mix-and-match가 되게 된다.

 

example

1. 하나의 그림이 있고, 그 그림에 tag를 달 수가 있다. tag를 하나 이상을 달 수 있으며, 그 tag는 각기 다른 tag를 다는 것이 가능하다.

 using System;

namespace PhotoDecorator
{
  public interface IPhoto
  {
    void Draw();
  }

  public class Photo : IPhoto
  {
    public void Draw()
    {
      Console.WriteLine("Draw Original Photo!!");
    }
  }

  public class Tag1 : IPhoto
  {
    IPhoto component;

    public Tag1(IPhoto component)
    {
      this.component = component;
    }
    public void Draw()
    {
      component.Draw();
      Console.WriteLine("Draw tag1 into photo");
    }
  }

  public class Tag2 : IPhoto
  {
    IPhoto component;

    public Tag2(IPhoto component)
    {
      this.component = component;
    }

    public void Draw()
    {
      component.Draw();
      Console.WriteLine("Draw tag2 into photo");
    }
  }

  public class Client
  {
    public static void DrawPicture(string pictureName, IPhoto photo)
    {
      Console.WriteLine( string.Format("Picture : {0}", pictureName));
      photo.Draw();
    }
  }

  public class Program
  {
    public static void Main(string[] args)
    {
      Photo originalPhoto1 = new Photo();
      Photo originalPhoto2 = new Photo();

      Tag1 tag1WithPhoto = new Tag1(originalPhoto1);
      Tag2 tag2WithPhoto = new Tag2(originalPhoto2);

      Client.DrawPicture("Original Photo with Tag1", tag1WithPhoto);
      Client.DrawPicture("Original Photo with Tag2", tag2WithPhoto);

      Console.ReadLine();

    }
  }
}

2. windows를 하나 설치 한 후에, MS SQL과 Visual Studio를 설치시켜줘야지되는 그러한 환경에 대한 구현을 생각해보자.

 using System;


namespace InstallWindows
{
  public interface IInstall
  {
    void Install();
  }

  public class WindowsInstaller : IInstall
  {
    #region IInstall Members

    public void Install()
    {
      Console.WriteLine("Windows Install complete");
    }

    #endregion
  }

  public class MsSqlInstaller : IInstall
  {
    IInstall windowsComponent;

    public MsSqlInstaller(IInstall component)
    {
      windowsComponent = component;
    }

    #region IInstall Members

    public void Install()
    {
      windowsComponent.Install();
      Console.WriteLine("MSSQL install complete!!");
    }

    #endregion
  }

  public class MsVisualStudioInstaller : IInstall
  {
    IInstall windowsComponent;
    public MsVisualStudioInstaller(IInstall component)
    {
      windowsComponent = component;
    }

    #region IInstall Members
    public void Install()
    {
      windowsComponent.Install();
      Console.WriteLine("MS Visual Studio Install complete!!");
    }
    #endregion
  }


  public class Client
  {
    public static void InstallWIndows(string target, IInstall component)
    {
      Console.WriteLine("====={0}=====", target);
      component.Install();
    }
  }

  public class Program
  {
    public static void Main()
    {
      WindowsInstaller windows1 = new WindowsInstaller();  //windows와 ms sql을 설치
      WindowsInstaller windows2 = new WindowsInstaller();  //windows와 ms visual studio를 설치
      WindowsInstaller windows3 = new WindowsInstaller();  //windows와 ms sql, ms visual studio를 설치

      MsSqlInstaller sqlInstaller = new MsSqlInstaller(windows1);
      Client.InstallWIndows("Windows with ms sql", sqlInstaller);

      MsVisualStudioInstaller vsInstaller = new MsVisualStudioInstaller(windows2);
      Client.InstallWIndows("Windows with visual studio", vsInstaller);

      MsVisualStudioInstaller vsAndSqlInstaller = new MsVisualStudioInstaller(new MsSqlInstaller(windows3));
      Client.InstallWIndows("Windows with ms sql and visual studio", vsAndSqlInstaller);

      Console.ReadLine();
    }
  }
}

 

Use

  • 하나의 기본적 동작이 있고, 그 동작에 대한 추가 동작에 대하여 만드는 것이 가능하다. (위의 예제와 같이, 윈도우를 하나 설치하고, 그 다음에 다른 동작을 행할 때, 그 윈도우에 대하여 동작을 넣는 것이 가능하다.)
  • C#의 I/O API가 Decorator pattern으로 되어 있다. System.IO.Stream에서 다른 Stream들은 IStream을 생성자로 받아서 동작을 행하도록 되어있다.
  • Windows control의 경우에, System.Windows.Control을 상속받는 class들은 모두 Decorator pattern으로 만들어져 있다. ListBox를 그린 후에 Border를 진하게 만들어 준다던가 하는 식의 동작들은 모두 Decorator pattern으로 구성되게 된다.

 

Summary

You have :
  • 기존에 존재하는 component가 subclassing이 불가능 할때.
You want to:
  • 추가적 상태나 동작을 추가하고 싶을때
  • 다른 class에 영향을 주지 않고, class에 변경을 가하고 싶을때,
  • 너무 많은 class가 영향을 받을 수 있기 때문에 subclassing을 피하고 싶을때
But consider using instead
  • 다른 class 사이에 interface를 넣는 adapter pattern이 될때
  • Composite pattern : interface의 상속없이 다른 class를 구현해서 동작의 결합이 가능할 때.
  • Proxy pattern : object에 대한 동작을 특별하게 만들어줘야지 될때.
  • Strategy pattern : wrapping하기 보다는 original object를 변경시키는 것이 더 나을때. 

 

 

Proxy Pattern

Role

객체에 대한 생성 및 접근을 제한을 줄 필요가 있을때 주로 사용된다. 이때 Proxy의 경우에는 작은 public method로 열린 객체로서 만들어지고, proxy로 보호되는 객체는 private로 닫힌 객체로서 생성 및 접근에 제한을 주게 된다.

 

Illustration

  Logon 되지 않은 상태에서는 View 상태만을 제공하고, 객체에 대한 생성 및 접근은 Logon이 된 상태에서 이루어지는 것과 비슷하게 구현되면 된다.

 

Example

1. Logon이 되지 않은 상태와 Logon이 된 상태에서의 View와 객체의 Request 의 제한을 주는 경우

using System;

namespace ProxyPattern
{
  public interface ISubject
  {
    void DoSomeThing();
  }

  internal class Subject
  {
    public void DoSomeThing()
    {
      Console.WriteLine("Do SomeThing in wanted object");
    }
  }

  public class ProtectionSubject : ISubject
  {
    private Subject subject; //Proxy에 의해서 접근 및 생성이 제한 받는 Class

    public bool Authenticate(string password)
    {
      if ( password.ToLower() == "password" )
      {
        subject = new Subject();
        return true;
      }
      else
      {
        subject = null;
        return false;
      }
    }
    #region ISubject Members

    public void DoSomeThing()
    {
      if ( subject != null )
      {
        subject.DoSomeThing();
      }
      else
      {
        Console.WriteLine("Authenticate First!!");
      }
    }

    #endregion
  }

  public class Program
  {
    public static void Main(string[] args)
    {
      ProtectionSubject protectionSubject = new ProtectionSubject();
      protectionSubject.DoSomeThing();
      protectionSubject.Authenticate("윤영권");
      protectionSubject.DoSomeThing();
      protectionSubject.Authenticate("password");
      protectionSubject.DoSomeThing();

      Console.ReadLine();
    }
  }
} 


2. Install이 되지 않은 상태에서 Computer를 접근하는 경우 (proxy와 decorator를 둘다 적용한 경우)

using System;

namespace ProxyAndDecorator
{
  public interface IWindowProxy
  {
    void DoSomeThing();
  }

  public interface IInstall
  {
    void Install();
  }

  public class WindowsProxy : IWindowProxy
  {
    IInstall installObject;

    public void CreateObject(string type)
    {
      if ( type == "none" )
      {
        installObject = new WindowsInstaller();
      }
      else if ( type == "mssql" )
      {
        WindowsInstaller windowsInstaller = new WindowsInstaller();
        installObject = new MsSqlInstaller(windowsInstaller);
      }
      else if ( type == "vs" )
      {
        WindowsInstaller windowsInstaller = new WindowsInstaller();
        installObject = new MsVisualStudioInstaller(windowsInstaller);
      }
    }

    #region IWindowProxy Members
    public void DoSomeThing()
    {
      if ( installObject != null )
      {
        IWindowProxy w = ( IWindowProxy )installObject;
        w.DoSomeThing();
      }
      else
      {
        Console.WriteLine("First CreateObject");
      }
    }
    #endregion
  }

  public class WindowsInstaller : IInstall, IWindowProxy
  {
    internal WindowsInstaller()
    {

    }
    #region IInstall Members
    public void DoSomeThing()
    {
      Console.WriteLine("Do Something in original windows");
    }

    public void Install()
    {
      Console.WriteLine("Windows Install complete");
    }

    #endregion
  }

  public class MsSqlInstaller : IInstall, IWindowProxy
  {
    IInstall windowsComponent;

    internal MsSqlInstaller(IInstall component)
    {
      windowsComponent = component;
    }

    #region IInstall Members

    public void Install()
    {
      windowsComponent.Install();
      Console.WriteLine("MSSQL install complete!!");
    }

    #endregion

    #region IWindowProxy Members

    public void DoSomeThing()
    {
      Console.WriteLine("Do Something in ms sql");
    }

    #endregion
  }

  public class MsVisualStudioInstaller : IInstall, IWindowProxy
  {
    IInstall windowsComponent;
    internal MsVisualStudioInstaller(IInstall component)
    {
      windowsComponent = component;
    }

    #region IInstall Members
    public void Install()
    {
      windowsComponent.Install();
      Console.WriteLine("MS Visual Studio Install complete!!");
    }
    #endregion

    #region IWindowProxy Members

    public void DoSomeThing()
    {
      Console.WriteLine("Do SomeThing in Visual Studio");
    }
    #endregion
  }


  public class Program
  {
    public static void Main()
    {
      WindowsProxy proxy = new WindowsProxy();

      proxy.DoSomeThing();

      proxy.CreateObject("none");
      proxy.DoSomeThing();

      proxy.CreateObject("mssql");
      proxy.DoSomeThing();

      proxy.CreateObject("vs");
      proxy.DoSomeThing();

      Console.ReadLine();
    }
  }
} 

 

Use

Proxy는 class에 접근할 수 있는 곳에 위치하게 된다. 이때 class는 민감한 데이터 영역이나, 느린 동작을 가진 것들을 주로 사용하게 된다. 대체적으로 Proxy로 사용되는 사항에서는 생성에서 무언가 제한을 준다던가, 데이터의 접근시에 무언가 제한을 줘야지 될때 주로 사용되어야지 된다.

 

Summary

You have objects that:
  • 생성에 비용이 많이 든다.
  • 접근제한이 필요하다.
  • Remote Site에서의 접근이 필요하다.

 

You want to:
  • 객체의 생성은 반드시 하나 이상의 동작을 통해서만 이루어지기를 원할때,
  • 객체의 접근에 대한 확인을 요구할때,
  • Local 객체가 remote 객체에 의한 접근을 행할때
  • 객체에 대한 접근 권한 설정을 줄때

 

 

Bridge Pattern

Role

Bridge Pattern은 구현과 추상화의 결합도를 낮추는데 목표가 있다. Bridge pattern은 기존에 존재하고 계속 남아있어야지 되는 구버젼에서 새로운 신버젼이 있을때에 유용한 패턴이다 .이미 구현된 추상화에 맞추어진 client code는 변경되지 않지만 client code는 자신이 원하는 어떤 버젼에서 동작될지를 결정해줘야지 된다.

Illustration

여러개의 .NET Framework가 있는 상황에서, S/W가 어떠한 .NET Framework를 골라서 사용해야지 될지를 고민하는 상황과 비슷하게 만들어진다.

Design

상속은 각기 다른 추상화의 구현에 대하여 일반적인 방법이다. 그렇지만, 구현은 추상화의 영역에 제한이 되게 되며, 추상화의 의존적인 상태에서의 변경은 제한적이 된다. Bridge pattern은  하나 이상의 추상화에 대한 상속을 통해 이미 구버젼의 추상화가 되어 있는 상황에서 추상화에 상속을 하는 것이다.  같은 추상화에서의 각각의 상속을 통해 추상화의 범위를 넓히는 것이 가능하다.

구현에 대한 동작을 Bridge에 위임하고, 동작에 대한 것을 따로 나누어주는 것으로서 구현하게 되는데, Bridge를 개선하는 것으로 전체 Class의 변경 없이 구현이 가능해진다.  

 

using System;

namespace BridgePattern
{
  public interface IBridge
  {
    void Add(string message);
    void Add(string friend, string message);
    void Poke(string who);
  }

  /// <summary>
  /// Bridge에 의하여 구현되어진 Class
  /// 구현에 의한 추상화만을 가지고 있고, 실질적인 동작은 Bridge에서 이루어지게 된다. 
  /// 같은 추상화에서 다른 동작을 행할때, Bridge를 변경시켜서 가능해진다.
  /// </summary>
  class Portal
  {
    readonly IBridge bridge;
    public Portal(IBridge bridge)
    {
      this.bridge = bridge;
    }

    public void Add(string message)
    {
      bridge.Add(message);
    }

    public void Add(string friend, string message)
    {
      bridge.Add(friend, message);
    }

    public void Poke(string who)
    {
      bridge.Poke(who);
    }
  }

  /// <summary>
  /// Proxy에 의해서 보호받는 Class
  /// </summary>
  class Subject
  {
    public string SubjectName { get; set; }
    public void DisplayName()
    {
      Console.WriteLine(SubjectName);
    }
  }

  /// <summary>
  /// Proxy
  /// </summary>
  class MySubject
  {
    private Subject subject;
    public void Authritize(string password)
    {
      subject = new Subject();
    }
    public void DisplayName()
    {
      if ( subject == null )
      {
        Console.WriteLine("Subject is null");
      }
      else
      {
        subject.DisplayName();
      }
    }
  }

  /// <summary>
  /// Bridge Pattern으로 만들어진 Class
  /// 실질적인 동작이 이루어지는 Class
  /// </summary>
  public class MyBook : IBridge
  {
    public string Name { get; set; }
    private MySubject mySubject;
    private static int users;

    public MyBook(string name, string password)
    {
      mySubject = new MySubject();
      Name = name;
      users++;

      mySubject.Authritize(password);
    }

    #region IBridge Members

    public void Add(string message)
    {
      mySubject.DisplayName();
      Console.WriteLine(message);
    }

    public void Add(string friend, string message)
    {
      mySubject.DisplayName();
      Console.WriteLine(string.Format("{0} : {1}", friend, message));
    }

    public void Poke(string who)
    {
      mySubject.DisplayName();
      Console.WriteLine(string.Format("{0}", who));
    }

    #endregion
  }

  /// <summary>
  /// C# 3.0 Extension : Class Extension, Bridge pattern에서 Bridge의 확장에 유용하게 사용할 수 있다.
  /// </summary>
  static class OpenBookExtensions
  {
    public static void SuperPoke(this Portal me, string who, string message)
    {
      me.Add(who, message);
    }
  }

  class BridgePattern 
  {
    public static void Main(string[] args)
    {
      Portal me = new Portal(new MyBook("Judith", "password"));
      me.Add("Hello world");
      me.Add("Today I worked 18 hours");
      Portal tom = new Portal(new MyBook("Tom", "password2"));
      tom.Poke("Judith-1");
      tom.SuperPoke("Judith-1", "hugged");
      tom.Add("Judith-1", "Poor you");
      tom.Add("Hey, I'm on OpenBook - it's cool!");
    }
  }
} 

Use

Bridge Pattern은 매우 간단하지만, 매우 강력한 Pattern이 된다. 하나의 구현에서 다른 구현을 이루고, 같은 추상화를 통해서 초기 디자인을 고려할 수 있기 때문이다. 잘 알려진 Bridge pattern은 Graphics에서 주로 사용된다. 각기 다른 Graphic 환경에서 다른 방법으로의 Display는 하나의 Display에 대한 추상화에서 각기 다른 Bridge를 통해서 Graphics를 구현하는 것이 가능하다.

You can:

Operation에 대한 구현을 언제나 알 필요가 없는 상태에서 Operation을 정의할 수 있다. 

You want to:
  • Client로부터 구현을 완벽하게 감출수 있다. (하나의 추상화에서 각기 다른 구현이 가능)
  • 추상화와 구현의 binding을 피할수 있다.
  • 추상화의 변경 없이, 구현을 변경하는 것이 가능하다.
  • 실행시에 각기 다른 Part와 연결이 가능하다. 
Posted by Y2K
,