Observer 를 만들어 보세요. #1
이번 포스팅에서는 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
우선, 기본적인 문법을 보시면
{
void Changed(Brush _brush);
}
이 경우 IBackground 를 상속받는 class는 반드시 Changed 메소드를 구현해야 하며, 상속받은 class 는 IBackground 타입으로 형변환이 가능하고, 그 경우 Changed 메소드가 노출되게 됩니다.
사실, 위에서 말씀드린 것 이외에 interface의 다른 큰 특징은 없습니다. 말씀드린게 전부이죠.
헌데, 몇가지 매우 핵심적인 사항이 있습니다.
이 경우 IBackground 를 상속받는 class는 반드시 Changed 메소드를 구현해야 하며, 상속받은 class 는 IBackground 타입으로 형변환이 가능하고, 그 경우 Changed 메소드가 노출되게 됩니다.
1. 메소드의 형식만 선언
2. 상속받은 class는 반드시 interface 에 정의 된 메소드를 구현
3. 상속받은 class는 interface 형으로 형변환이 가능
저는, Interface를 가장 쉬이 이해할 수 있는 한 마디가 바로 "표준"이라고 생각하는데요, 예를 들어 USB 포트를 들어 보면..
USB 포트는 일정한 규격, 또는 표준이라고 할 수 있습니다. 어떠한 디바이스 건 USB 포트의 형식에 맞춰 디바이스와 연결할 수 있도록 셋팅 해 둔다면, USB에 꽂는 즉시 디바이스를 인식하고 전원이 공급되게 되죠. 여기서 USB 포트는 표준을 제시하고, 디바이스 벤더는 USB 표준에 맞게 설계를 할 것이고요.
interface도 마찬가지로 "표준"이라고 생각하실 수 있습니다.
BlueUserControl class와 RedUserControl class 두 개의 UserControl 클래스가 있습니다.
<BlueUserControl.cs>
{
public BlueUserControl()
{
InitializeComponent();
}
#region IOption Members
public void Changed(Brush _brush)
{
this.LayoutRoot.Background = _brush;
}
#endregion
}
<RedUserControl.cs>
{
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 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();
}
}
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 를 만드는 방법을 포스팅 해 보겠습니다.