重構到觀察者模式 Refactor to Observer Pattern
這個軟體功能很簡單:從一個指定的埠接收資訊(8888埠)。程式碼如下:
{
//用來接收資訊
public void Receive()
{
Byte[] receiveBytes;
string receiveString;
UdpClient udpClientB = new UdpClient();
try
{
IPEndPoint RemoteIpEndPoint = new IPEndPoint(IPAddress.Any, 8888);
udpClientB.Client.Bind(RemoteIpEndPoint);
//一直等待著,直到從指定埠收到資訊。
while (true)
{
receiveBytes = udpClientB.Receive(ref RemoteIpEndPoint);
receiveString = Bytes2String(receiveBytes);
//把接收到的字串在視窗上列印出來。
Console.WriteLine(receiveString);
}
}
catch (Exception ex) { Console.WriteLine(ex.Message); }
finally { udpClientB.Close(); }
}
//用來把接收到的bytes轉換為string
string Bytes2String(Byte[] bytes)
{
MemoryStream ms = new MemoryStream(bytes, 0, bytes.Length);
BinaryFormatter bf = new BinaryFormatter();
return (string)bf.Deserialize(ms);
}
}
static void Main(string[] args)
{
Receiver recieve = new Receiver();
recieve.Receive();
}
第一次需求改變
原來的需求:把接收到的資料在螢幕上列印出來。
現在的需求:要求我們把接收到的資料寫到log檔案裡。
為了方便下一次還有類似的改動,我們把寫的log的操作從方法Receive()裡抽出來放到獨立的方法ProcessRecievedData裡。如下
{
using (StreamWriter sw = File.AppendText("Receive.log"))
{
sw.WriteLine(string.Format("Receive <> at {1}", rString, DateTime.Now));
}
}
原來的 Console.WriteLine(receiveString) 的地方也就變成了對ProcessRecievedData的呼叫。這是我們遇到的第一個變化點,我們用Extract Method的思路,將變化封裝到一個方法裡面,這是最基本最簡單的重構思路。
第二次需求改變:因為接收資訊的功能很常用,為了方便複用,我們決定把它放到一個獨立的Assembly裡,供其別人呼叫。
為了實現這一目標,我們仔細分析了一下,需要做到以下兩點:
1.要為接收到的各種型別的資料提供支援。
2.要能夠在接收到資料後,通知客戶程式,從而讓客戶程式對收到的資料進行相應的處理。
對於第1點,由於.net 2.0支援泛型,我們可以很容易通過定義一個泛型的Receiver,從而把型別作為引數從而支援接收到的各種型別的資料。
對於第2點,也就是observer模式的目的,其實現過程涉及到訊息的訂閱和退訂,還有什麼釋出(publish)、訂閱(subscribe)、以及(Subject)和觀察者(Observer)等詞彙。這些對於初學者來講,起碼對當初的我來說有些繞。
我們先來看看比較直觀的做法。仔細分析一下上面的第2點,這個地方的變化點有兩個:
1. 在收到資料後,客戶程式碼的處理方式會是不同的。
2. 在收到資料後,客戶程式碼要進行處理的方式的個數是變化的、不固定的。
既然我們的目的無非是讓客戶程式對接收到的資料進行處理,那麼我們在收到資料後直接呼叫客戶的處理方法不就行了。結合要封裝第1個變化點的考慮,我們決定在類Receiver裡放一個成員物件,它是客戶程式定義的某個型別,讓客戶程式(也就是使用這個Assembly的上層程式碼)為這個變數賦值,然後我們再呼叫這個物件的某個方法。具體說來就是定義一個介面,讓客戶實現這個介面,然後我們的在收到資料的時候,呼叫這個介面裡的方法。如下:
{
void Process(string sString);
}
class Receiver
{
public IProcess ProcesseData;
……………
……………
void ProcessRecievedData(string rString)
{
ProcesseData.Process(receiveString);
}
}
只要客戶程式碼實現了IProcess 的Process方法,他就能在收到資料是被我們的程式碼呼叫,也就是在時間發生時受到了我們的通知。客戶方使用的程式碼如下:
{
public void Process(string s)
{
using (StreamWriter sw = File.AppendText("Receive.log"))
{
sw.WriteLine(string.Format("Receive <> at {1}", s, DateTime.Now));
}
}
}
static void Main(string[] args)
{
Receiver recieve = new Receiver();
recieve.ProcesseData = new WriteToFile();
recieve.Receive();
}
再看第2個變化點:在收到資料後,客戶程式碼要進行處理的操作的個數是變化的、不固定的。這個很簡單,我們只需要把在Receiver 宣告的ProcesseData改成連結串列就行了:
{
public List<IProcess> mProcesses=new List<IProcess> ();
……………
這樣一來,我們的ProcessRecievedData也要做相應的更改:
{
if (mProcesses.Count > 0)
{
foreach (IProcess mprocess in mProcesses)
{
mprocess.Process(rString);
}
}
}
呼叫過程也跟著變:
{
public void Process(string s)
{
Console.WriteLine(s);
}
}
static void Main(string[] args)
{
Receiver recieve = new Receiver();
recieve.mProcesses.Add(new CosoleDisplay());
recieve.mProcesses.Add(new WriteToFile());
recieve.Receive();
}
好了,讓我們先來回頭看看Observer Pattern要解決的是什麼問題,怎麼解決的。
Observer(觀察者)模式的定義:定義物件間的一種一對多的關係,當一個物件的狀態發生改變時,所有依賴它的物件都得到通知,並被自動更新.
我們進一步來看這裡的變化點是什麼?需要注意到雖然“物件的狀態”發生了變化,但是“物件的狀態”本身卻並不是我們開發過程中需要捕捉的變化點。在這兒,狀態發生變化這一事實是穩定的,所以我們的涉及到狀態變化的程式碼一直沒有重構過,在我們的例子中是Receive方法中的大部分程式碼。
發生變化的有兩個:
1依賴狀態變化這一事件的物件們。
2這些物件對與狀態變化後採取的動作。
我們用語言的多型(介面)和類庫的連結串列方便的將這兩個變化點封裝到一個成員mProcesses裡。從而,所有這一過程中的變化都可以通過對mProcesses這一個成員的操作來加以實現。這也就大大降低了Observer類和呼叫程式之間的耦合性。
所以說我們用了模式的方法達成了Observer的目的,也就是實現了Observer Pattern。
因為Oberser模式要解決的問題具有非常的普遍性,.net提供了語言上的支援delegate以及event。Delegate使用起來更方便,因為介紹delegate和event的文章已經很多了,這就不再介紹了。最後給出delegate版的支援泛型的完整的程式碼。
<!--
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
-->Receiver-T.cs
class Receiver<T>
{
public delegate void delegateProcess(T sString);
public event delegateProcess mProcesses;
//用來接收資訊
public void Receive()
{
Byte[] receiveBytes;
T receiveData;
UdpClient udpClientB = new UdpClient();
try
{
IPEndPoint RemoteIpEndPoint = new IPEndPoint(IPAddress.Any, 8888);
udpClientB.Client.Bind(RemoteIpEndPoint);
//一直等待著,直到從指定埠收到資訊。
while (true)
{
receiveBytes = udpClientB.Receive(ref RemoteIpEndPoint);
receiveData = Bytes2Data(receiveBytes);
ProcessRecievedData(receiveData);
}
}
catch (Exception ex) { Console.WriteLine(ex.Message); }
finally { udpClientB.Close(); }
}
//用來把接收到的bytes轉換為string
T Bytes2Data(Byte[] bytes)
{
MemoryStream ms = new MemoryStream(bytes, 0, bytes.Length);
BinaryFormatter bf = new BinaryFormatter();
return (T)bf.Deserialize(ms);
}
void ProcessRecievedData(T rData)
{
if (mProcesses != null)
{
mProcesses(rData);
}
}
}
run.cs
class run
{
[STAThreadAttribute]
static void Main(string[] args)
{
Receiver<string> recieve = new Receiver<string>();
CProcess c = new CProcess();
recieve.mProcesses += new Receiver<string>.delegateProcess(c.CosoleDisplay);
recieve.mProcesses += new Receiver<string>.delegateProcess(c.Sent2ClipBoard);
recieve.mProcesses += new Receiver<string>.delegateProcess(c.WriteToFile);
recieve.Receive();
}
}
CProcess.cs
class CProcess
{
public void CosoleDisplay(string s)
{
Console.WriteLine(s);
}
public void WriteToFile(string s)
{
using (StreamWriter sw = File.AppendText("Receive.log"))
{
sw.WriteLine(string.Format("Receive <> at {1}", s, DateTime.Now));
}
}
public void Sent2ClipBoard(string s)
{
Clipboard.SetText(s);
}
}
當然最後要給出傳送方的程式碼來測試接受是否能夠成功。
<!--
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
--> class Sender
{
public void Send(string obj)
{
UdpClient udpClientA = new UdpClient();
try
{
udpClientA.Connect("127.0.0.1", 8888);
IPEndPoint RemoteIpEndPoint = new IPEndPoint(IPAddress.Any, 8888);
byte[] sendBytes = String2Bytes(obj);
udpClientA.Send(sendBytes, sendBytes.Length);
}
catch (Exception ex) { Console.WriteLine(ex.Message); }
finally { udpClientA.Close(); }
}
//用來把要發出的字串轉換為bytes
byte[] String2Bytes(string sString)
{
MemoryStream ms = new MemoryStream();
try
{
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(ms, sString);
ms.Close();
}
catch (Exception ex) { Console.WriteLine(ex.Message); }
return ms.ToArray();
}
}
現在回過頭來再看Observer Pattern 的 UML圖,是不是感到非常清晰了。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/12639172/viewspace-629782/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 設計模式之觀察者模式(Observer Pattern)設計模式Server
- C#設計模式(17)——觀察者模式(Observer Pattern)C#設計模式Server
- 實現觀察者模式(Observer Pattern)的2種方式模式Server
- observer-觀察者模式Server模式
- 重構 - 觀察者模式模式
- 設計模式:觀察者模式(observer)設計模式Server
- Visitor模式和Observer觀察者模式模式Server
- 人人都會設計模式—觀察者模式–Observer設計模式Server
- 人人都會設計模式---觀察者模式--Observer設計模式Server
- C#設計模式系列:觀察者模式(Observer)C#設計模式Server
- 使用C# (.NET Core) 實現觀察者模式 (Observer Pattern) 並介紹 delegate 和 eventC#模式Server
- 原生實現的觀察者模式(Observer Model)模式Server
- 剪貼簿中的觀察者(Observer)模式Server模式
- Head First 設計模式(2)---觀察者(Observer)模式設計模式Server
- 設計模式-- 觀察者模式Observer(物件行為型)設計模式Server物件
- Java中使用Observer介面和Observable類實踐Observer觀察者模式JavaServer模式
- Java23種設計模式【22】----》觀察者模式(Observer)Java設計模式Server
- 設計模式(三)觀察者模式Observer(釋出訂閱)設計模式Server
- 構建者模式(Builder pattern)模式UI
- Observer觀察者模式與OCP開放-封閉原則Server模式
- 重識設計模式-建造者模式(Builder Pattern)設計模式UI
- 【 應用以及剖析】之 java.util.Observer 觀察者模式實現JavaServer模式
- 觀察者模式——從JDK到Spring模式JDKSpring
- 觀察者Observer模型事件,使用注意情況Server模型事件
- observer pattern 之我見Server
- 觀察者模式模式
- 建造者模式(Builder Pattern)模式UI
- 中介者模式(Mediator Pattern)。模式
- PHP觀察者模式PHP模式
- Unity——觀察者模式Unity模式
- 觀察者模式(2)模式
- Java 觀察者模式Java模式
- JS 觀察者模式JS模式
- 設計模式----觀察者模式設計模式
- 設計模式 —— 觀察者模式設計模式
- 設計模式(觀察者模式)設計模式
- 設計模式——觀察者模式設計模式
- 設計模式-觀察者模式設計模式