代理模式

23種常用的面向對象軟件的設計模式之一

組成:

抽象角色:通過接口或抽象類聲明真實角色實現的業務方法。

代理角色:實現抽象角色,是真實角色的代理,通過真實角色的業務邏輯方法來實現抽象方法,並可以附加自己的操作。

真實角色:實現抽象角色,定義真實角色所要實現的業務邏輯,供代理角色調用。

基本代碼組成:

class Program
    {
        static void Main(string[] args)
        {
            Proxy proxy = new Proxy();
            proxy.Request();

            Console.Read();
        }
    }

    //定義了RealSubject和Proxy的共用接口,在任何使用RealSubject的地方都可以用Proxy
    abstract class Subject
    {
        public abstract void Request();
    }

    //定義Proxy 所代表的真實實體
    class RealSubject : Subject
    {
        public override void Request()
        {
            Console.WriteLine("真實的請求");
        }
    }

    //保存一個引用使得代理可以訪問實體,並提供一個與 Subject的接口相同的接口,這樣代理就可以用來替代實體
    class Proxy : Subject
    {
        RealSubject realSubject;
        public override void Request()
        {
            if (realSubject == null)
            {
                realSubject = new RealSubject();
            }

            realSubject.Request();
        }
    }
以上代碼出自《大話設計模式》


網上有一篇在unity中使用代理模式,原文鏈接:http://www.xuanyusong.com/archives/1895/


昨天有個朋友問我在Unity3D中如何使用代理。我簡單的給他說了一下,可是他還是希望我能寫出來。既然不是很麻煩,那麼我就在博客裏把這個例子寫出來。C#語言是支持代理的,並且代理是非常的好用的一種方式。簡單的來說就是 你委託朋友幫你做一件事情,當你的朋友做完以後會告訴你。 代碼中一般A、B、C、D若干類同時委託E類來做一件事情,當這件事情E類完成時會同時回調A、B、C、D類中的方法,大概就是這個意思拉。


回到Unity3D中,我們寫一個簡單的例子,Test.cs委託Log.cs 辦一件事情,當Log.cs辦完後在Test.cs回調委託的方法。代碼比較簡單我們直接將Test.cs Log.cs 綁在攝像機上,運行就能看到效果。。上代碼!!!


Log.cs

using UnityEngine;
using System.Collections;
public class Log :MonoBehaviour
{
	public delegate void DoOnething(string str);
	public static event DoOnething doOne;
 
	public delegate void DoTwothing(string str);
	public static event DoTwothing doTwo;
 
	void Start ()
	{
		if(doOne != null)
		{
			doOne("yusong");
		}
 
		if(doTwo != null)
		{
			doTwo("momo");
		}
	}
 
}
我解釋一下。委託的標識符就是delegate ,上述代碼的DoOnething(string str) DoTwothing(string str)就是委託完畢後回調的方法名稱,doOne doTwo這兩個是委託的事件,在Start方法中通過委託的事件將委託的參數傳遞進去即可實現委託。在開發中應該有一個專門用來委託的類,在DoOne DoTwo方法之上應該執行對應對託的相關代碼。不一定是在Start方法中,靜態調用對象皆可。


接着再看看Test.cs

using UnityEngine;
using System.Collections;
 
public class Test : MonoBehaviour
{
	void OnEnable()
    {
		Log.doOne += DoOnething;
		Log.doTwo += DoTwothing;
    }
 
    void OnDisable()
	{
		Log.doOne -= DoOnething;
		Log.doTwo -= DoTwothing;
	}
 
	public void DoOnething(string str)
	{
		Debug.Log("DoOnething " + str);
	}
 
	public void DoTwothing(string str)
	{
		Debug.Log("DoTwothing " + str);
	}
}
在OnEnabel和OnDisable中啓動與關閉委託,這也是爲什麼Log.cs中要判斷一下委託是否爲空。如果爲空的話是會有錯誤的。比如


Log.doOne += DoOnething;


這段代碼的意思就是爲委託事件賦值,讓本類的DoOnethine(string str)方法接受委託傳遞回來的事件,這裏起到一個回調的作用。


Log.doOne -= DoOnething;


就是釋放當前的委託事件,因爲Log.doOne是一個靜態類型的變量,不用的時候把他關掉,一旦關掉後,DoOnething方法就接收不到回調信息了,除非再次 += 添加。


最後就是當Log.cs中調用 doOne(“yusong”);的時候,實際上就是回調Test.cs中的 DoOnething(string str);


關於詳細代理和事件的區別,下面有篇文章,寫的很不錯,轉給大家。


C#的代理和事件


原文: http://www.yaosansi.com/post/1119.html


重新溫習下,剛好看見,轉過來.
==============================================


作者: Eric Gunnerson 


代理 (Delegate)
大多數情況下,當調用函數時我們會指定要直接調用的函數。比如類 MyClass 如具有一個名爲 Process 的函數,我們通常會按如下方法進行調用:


MyClass myClass = new MyClass();
myClass.Process();


這種調用在大多數情況下都是可行的。但是有些時候,我們不想直接調用函數,而希望能夠將它傳遞給其他人,讓他們進行調用。在以事件驅動的系統(如圖形用戶 界面)中,這種方法尤爲有用。例如當我需要在用戶單擊某個按鈕即可執行一些代碼時,或者當我要記錄一些信息但卻無法指定記錄方式時。
考慮以下示例:


public class MyClass
{
public void Process()
{
Console.WriteLine(“Process() begin”);
// 這裏還有其他東西…
Console.WriteLine(“Process() end”);
}
}


在此類中,我們進行一些記錄,以瞭解函數的開始時間和結束時間。但是,我們的記錄僅限於發送到控制檯,這可能不是我們所需要的。我們真正需要的是能夠控制從函數外部記錄信息的位置,同時不必使函數代碼變得複雜。


在這種情況下,代理便是理想的解決方法。代理使我們可以指定將要調用的函數,看起來好像不需要指定哪個函數一樣。對代理的聲明類似於對函數的聲明,不同的是在這種情況下,我們所聲明的是此代理可引用的函數簽名。


我們的例子將聲明一個帶有單個字符串參數且沒有返回類型的代理。修改該類如下:


public class MyClass
{
public delegate void LogHandler(string message);
public void Process(LogHandler logHandler)
{
if (logHandler != null)
logHandler(“Process() begin”);
// 這裏還有其他東西
if (logHandler != null)
logHandler (“Process() end”);
}
}


使用代理與直接調用函數相似。只是在調用函數前,我們需要檢查代理是否爲空(即不指向一個函數)。


要調用 Process() 函數,我們需要聲明一個與代理相匹配的記錄函數,然後創建代理的實例,以指向該函數。然後,將此代理傳遞給 Process() 函數。


class Test
{
static void Logger(string s)


{
Console.WriteLine(s);
}


public static void Main()
{
MyClass myClass = new MyClass();


MyClass.LogHandler lh = new MyClass.LogHandler(Logger);


myClass.Process(lh);
}
}


Logger() 函數是一個我們要從 Process() 函數中調用的函數,我們對它進行了聲明,使其與代理相匹配。在 Main() 中,我們創建代理的一個實例,然後將該函數傳遞給代理構造函數,使其指向該函數。最後,我們將代理傳遞給 Process() 函數,該函數接着調用 Logger() 函數。


如果您習慣於使用 C++ 語言,您可能會認爲代理很像函數指針,這種想法非常接近於事實。但是,代理並不“僅僅”是函數指針,它還提供了其它多種功能。


傳遞狀態 (Passing State)


在上面的簡單示例中,Logger() 函數僅僅是輸出些字符串。一個不同的函數可能把信息記錄到文件中,但是要進行這種操作,該函數需要知道把信息寫道什麼文件中。


對於 Win32® 而言,當您傳遞函數指針時,可隨之傳遞狀態。但是對於 C#,這就沒有必要了,因爲代理既可指向靜態函數,“也”可指向成員函數。以下是一個有關如何指向成員函數的示例:


class FileLogger
{
FileStream fileStream;
StreamWriter streamWriter;


public FileLogger(string filename)
{
fileStream = new FileStream(filename, FileMode.Create);
streamWriter = new StreamWriter(fileStream);
}


public void Logger(string s)
{
streamWriter.WriteLine(s);
}


public void Close()
{
streamWriter.Close();
fileStream.Close();
}
}


class Test
{
public static void Main()
{
FileLogger fl = new FileLogger(“process.log”);


MyClass myClass = new MyClass();


MyClass.LogHandler lh = new MyClass.LogHandler(fl.Logger);


myClass.Process(lh);
fl.Close();
}
}


FileLogger 類僅封裝文件。我們修改了Main()以使代理指向 FileLogger 的 fl 實例的 Logger() 函數。當從 Process() 中激活此代理時,將會調用成員函數並把字符串記錄到相應的文件中。


其優點在於,我們不必更改 Process() 函數 -對代理來說代碼都是相同的,無論引用的是靜態函數還是成員函數,。


多播 (Multicasting)
雖然指向成員函數的功能已讓人感到滿意,但利用代理,您還可以巧妙地完成其它一些任務。在 C# 中,代理是“多播”的,這表示它們可同時指向一個以上的函數(即基於 System.MulticastDelegate 類型)。多播代理將維護一個函數列表。當調用該代理時,將會調用列表中的所有函數。我們可以添加第一個示例中的記錄函數,然後調用這兩個代理。要將兩個代理組合起來,可使用 Delegate.Combine() 函數。其代碼如下:


MyClass.LogHandler lh = (MyClass.LogHandler)
Delegate.Combine(new Delegate[]
{new MyClass.LogHandler(Logger),
new MyClass.LogHandler(fl.Logger)});


啊呀,真的是很難看!幸好 C# 提供了一種更好的語法,而不用將以上語法強加給用戶。無需調用 Delegate.Combine(),僅使用 += 即可組合這兩個代理:


MyClass.LogHandler lh = null;
lh += new MyClass.LogHandler(Logger);
lh += new MyClass.LogHandler(fl.Logger);


這樣就簡潔多了。要從多播代理中刪除一個代理,可調用 Delegate.Remove() 或使用 -= 運算符(我知道自己會用哪一個)。
當你調用多播代理時,就會按出現順序對調用列表中的代理進行同步調用。如果此過程中出現了錯誤,執行過程即被中斷。
如果您想更嚴格地控制調用順序(例如要進行萬無一失的調用),則可以從代理中獲取調用列表,然後自行調用這些函數。以下是一個示例:


foreach (LogHandler logHandler in lh.GetInvocationList())
{
try
{
logHandler(message);
}
catch (Exception e)
{
// 在這裏處理異常情況嗎?
}
}


代碼只是將每次調用包裝在一個 try-catch 對中,這樣在一個調用處理(handler)中引發的異常就不會妨礙對其它調用處理(handler)的激活。


事件 (Events)


我們已經對代理進行了較長時間的討論,現在該談一談事件了。一個顯而易見的問題就是:“既然我們已經有了代理,爲什麼還需要事件?”


回答這個問題的最好方法就是考慮用戶界面對象所發生的事件。例如,一個按鈕可能有公共的“Click”代理。我們可將一個函數掛接到該代理上,這樣當單擊此按鈕時,就可以調用該代理。例如:


Button.Click = new Button.ClickHandler(ClickFunction);


它表示當單擊此按鈕時,將調用 ClickFunction()。


小測驗:上述代碼是否存在問題?我們忘記了什麼?
答案是,我們忘記使用 += 而直接分配了代理。這表示其它任何掛接到“Button.Click”的代理現在都將解除掛接。“Button.Click”應該是公共的,以便其它對象可以對其進行訪問,因此上述情況將無法避免。同樣,要刪除代理,用戶可能會編寫以下代碼:
Button.Click = null;
這將刪除所有代理。


這些情形極其糟糕,因爲在許多情況下只掛接了一個代理,問題不會明顯地表現爲bug。隨後,當掛接了另一個代理時,事情就糟了!


事件在代理模型上添加了一層保護。這裏有一個支持事件的對象例子:


public class MyObject
{
public delegate void ClickHandler(object sender, EventArgs e);
public event ClickHandler Click;


protected void OnClick()
{
if (Click != null)
Click(this, null);
}
}


ClickHandler 代理使用事件代理的標準模式來定義事件的簽名。它以handler的名字結尾,帶有兩個參數。第一個參數是發送此事件的對象,第二個參數用於傳遞伴隨事件發生的信息。本例中沒有要傳遞的信息,因此直接使用 EventArgs;但是如果有數據要傳遞,則使用從 EventArgs 派生的類(例如 MouseEventArgs)。


“Click”事件的聲明做兩件事情:首先,它聲明一個名爲“Click”的代理成員變量,在類的內部使用。其次,它聲明一個名爲“Click”的事件,該事件可按照常規訪問規則從類的外部進行使用(在此例中,事件爲公共事件)。
一個像OnClick()這樣的函數通常包含進去以便該類型或從該類型的派生類型可以觸發事件。由於“Click”是代理,您將會注意到,用來觸發事件的代碼與代理的代碼相同。


與代理類似,我們使用 += 和 -= 來掛接或解除事件掛接,但與代理不同的是,僅可對事件執行這些操作。這可確保不會發生先前所討論的兩種錯誤。


使用事件是很簡單的事情。


class Test
{
static void ClickFunction(object sender, EventArgs args)
{
// process the event here.
}
public static void Main()
{
MyObject myObject = new MyObject();


myObject.Click += new MyObject.ClickHandler(ClickFunction);
}
}


我們創建一個與代理簽名相匹配的靜態函數或成員函數,然後用 += 向事件中添加代理的一個新實例。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章