본문 바로가기

silverlight

Observer 를 만들어 보세요. #1

객체지향 언어를 사용하시는 많은 개발자분들은 좀 더 객체지향적인 코드를 원하곤 합니다. 여기서, 좀 더 객체지향적인 코드란 것은 객체지향이 추구하는 언어적인 특색을 잘 살리는 코드를 말하는 것이죠. 그래서, 많은 분들이 클래스 다운 클래스를 만드려고 하고, Design Pattern 등에 관심도 보이고 합니다. 또한, 객체의 생성과 소멸 등에 관해서도 관심을 갖게 되고요 :)

이번 포스팅에서는 Design Pattern 의 하나인 Observer Pattern과 Singleton Pattern을 포스팅 해 보려고 합니다. 그러나, 여기서 Observer Pattern과 Singleton Pattern을 사용하고 안하고의 유무에 따라서 코드가 좀 더 객체지향적이라고 할 수 있는 것은 아닙니다. 좀 더 크게 Design Pattern의 사용 유무가 객체지향적이다 아니다라고 할 순 없다는 것이죠.

또한, Design Pattern 을 접하시는 대부분의 개발자분들은, Design Pattern 에서 정의하는 Command Pattern, Factory Pattern, Observer Pattern 등의 용어만 접할 뿐, 이미 사용하고 있는 로직인 경우도 많습니다. 그래서 어떤 분들은, Design Pattern 을 로직이나 알고리즘 수준이 아닌, 개발자간의 커뮤니케이션에 필요한 용어 정도로 생각하시는 분들도 많습니다.

다만, Design Pattern 을 배움에 있어 객체 지향 언어의 특색을 전제해야 하는 것들이 있습니다. 바로 이런 특색들을 개념적으로 습득하시는 일련의 과정이 큰 도움이 될 것이라 생각합니다.

이번 포스팅에서 소개 해 드릴 Observer Pattern과 Singleton Pattern 은, Interface 나 abstract class 같은 추상 클래스에 대한 개념적 이해를 전제해야 합니다. 그럼, 간단히 Observer Pattern 을 포스팅하기 위한 목차로 시작해 보겠습니다.

1. Interface
2. Observer
3. Observer Control class
4. Singleton Pattern

Interface
우선, 기본적인 문법을 보시면
public interface IBackground
{
    void Changed(Brush _brush);
}
IBackground 라는 interface를 선언 한 뒤 IBackground 블럭 내에서 반환타입이 void 이며 파라미터로 Brush 타입의 인자를 받는다는 메소드의 형식만 선언하게 됩니다.
이 경우 IBackground 를 상속받는 class는 반드시 Changed 메소드를 구현해야 하며, 상속받은 class 는 IBackground 타입으로 형변환이 가능하고, 그 경우 Changed 메소드가 노출되게 됩니다.

사실, 위에서 말씀드린 것 이외에 interface의 다른 큰 특징은 없습니다. 말씀드린게 전부이죠.
헌데, 몇가지 매우 핵심적인 사항이 있습니다.
IBackground 라는 interface를 선언 한 뒤 IBackground 블럭 내에서 반환타입이 void 이며 파라미터로 Brush 타입의 인자를 받는다는 메소드의 형식만 선언하게 됩니다.
이 경우 IBackground 를 상속받는 class는 반드시 Changed 메소드를 구현해야 하며, 상속받은 class 는 IBackground 타입으로 형변환이 가능하고, 그 경우 Changed 메소드가 노출되게 됩니다.
바로, 다음의 3가지 사항입니다.
1. 메소드의 형식만 선언
2. 상속받은 class는 반드시 interface 에 정의 된 메소드를 구현
3. 상속받은 class는 interface 형으로 형변환이 가능

저는, Interface를 가장 쉬이 이해할 수 있는 한 마디가 바로 "표준"이라고 생각하는데요, 예를 들어 USB 포트를 들어 보면..

USB 포트는 일정한 규격, 또는 표준이라고 할 수 있습니다. 어떠한 디바이스 건 USB 포트의 형식에 맞춰 디바이스와 연결할 수 있도록 셋팅 해 둔다면, USB에 꽂는 즉시 디바이스를 인식하고 전원이 공급되게 되죠. 여기서 USB 포트는 표준을 제시하고, 디바이스 벤더는 USB 표준에 맞게 설계를 할 것이고요.

interface도 마찬가지로 "표준"이라고 생각하실 수 있습니다.
BlueUserControl class와 RedUserControl class 두 개의 UserControl 클래스가 있습니다.

<BlueUserControl.cs>
public partial class BlueUserControl : UserControl, IBackground
{
    public BlueUserControl()
    {
        InitializeComponent();
    }

    #region IOption Members 

    public void Changed(Brush _brush)
   {
        this.LayoutRoot.Background = _brush;
    } 

    #endregion
}


<RedUserControl.cs>
public partial class RedUserControl : UserControl, IBackground
{
    public RedUserControl()
    {
        InitializeComponent();
    } 

    #region IOption Members
 
    public void Changed(Brush _brush)
    {
        this.LayoutRoot.Background = _brush;
    } 

    #endregion


두 class 모두 IBackground interface 를 상속 받고, 반드시 구현해야 할 interface 내에서 정의한 메소드인 public void Changed(Brush _brush) 를 구현하고 있습니다.

MainPage의 Grid에 두 클래스를 배치 해 보도록 하죠.
<MainPage.xaml>

<UserControl x:Class="ObserverPattern.MainPage"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

    xmlns:uc="clr-namespace:ObserverPattern.UserControls"

    mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">

    <Grid x:Name="LayoutRoot" Background="Black">

        <Grid.ColumnDefinitions>

            <ColumnDefinition/>

            <ColumnDefinition/>

        </Grid.ColumnDefinitions>

        <uc:BlueUserControl x:Name="blueUserControl" Grid.Column="0"/>

        <uc:RedUserControl x:Name="redUserControl" Grid.Column="1"/>

        <Button x:Name="OptionChangeButton" Content="Changed" Height="30"

                HorizontalAlignment="Left"

                VerticalAlignment="Top"

                Margin="10,10,0,0"

                Click="OptionChangeButton_Click"/>

    </Grid>

</UserControl>


MainPage를 실행하면 두개의 컬럼으로 이뤄진 Grid 에 한쪽은 Blue, 한쪽은 Red로 실행 됩니다.


그럼, IBackground 를 상속받아 구현 된 두 UserCotnrol을 Changed 버튼을 클릭 했을 때, RedUserControl은 Blue로, BlueUserControl은 Red 로 바꿔 보도록 하겠습니다.
<MainPage.cs>
public partial class MainPage : UserControl
{
    public MainPage()
    {
        InitializeComponent();
    } 

    IBackground option;
    public void optionChange()
    {
        SolidColorBrush brush = new SolidColorBrush(Colors.Blue);
        option = this.redUserControl;
        option.Changed(brush);
 
        brush = new SolidColorBrush(Colors.Red);
        option = this.blueUserControl;
        option.Changed(brush);
    }
 
    private void OptionChangeButton_Click(object sender, RoutedEventArgs e)
    {
        this.optionChange();
    }
}

Changed 버튼을 클릭 시, optionChanged 메소드가 실행 됩니다.
optionChanged 메소드에서는 UserControl 을 IBackground 형의 option 으로 할당한 뒤 option 객체의 Changed 메소드를 실행하게 되죠.
두 UserControl은 IBackground 형을 상속받았기 때문에 IBakground 형의 option으로 형변환이 가능하며, IBackground 에서 정의 된 메소드를 반드시 구현 했기 때문에, option 객체에 노출 된 Changed 메소드를 실행하게 됩니다.


USB 포트를 비교했었는데요, USB 포트의 표준에 맞춰 설계 된 디바이스르 USB 포트에 연결하면,
운영체제에서 디바이스를 인식하고, Driver 를 자동으로 설치하거나, Driver 를 설치하도록 지시하게 됩니다.
이와 마찬가지로, MainPage 에서 발생한 이벤트이지만, IBackground 를 상속받은 클래스의 객체에서 직접 객체 자신의 메소드를 통해 객체 자신의 속성을 변경하게 됩니다. 이는 interface 를 상속 받고, 반드시 구현 된 메소드가 존재하기 때문이죠.

아~ 여기서 제가 Observer 를 포스팅할까 했던 이유 중 하나인데요. 바로, 서로 다른 객체간의 통신(넓은 의미로 ㅎ)을 가능하게 하는 방법 중 하나가 interface의 활용이 될 수 있습니다. :)

다음에는 이런 interface의 특징을 활용한 Observer 를 만드는 방법을 포스팅 해 보겠습니다.