Design Pattern : Momento #2

    เมื่อตอนที่ผมนำเสนอเรื่อง Momento ก็ได้รับ email ฉบับหนึ่งจากคุณ Atipon ลองเอา Code ให้ผมดูดังนี้ครับ

using System;
abstract class PersistentObject
{
	private int _intSQLTimeOut = 120;	
	public PersistentObject() { }
	public void SetTimeOut(int @intTimeOut)
	{
		_intSQLTimeOut = @intTimeOut;
	}

	public int GetTimeOut()
	{
		return _intSQLTimeOut;
	}	
 	private  PersistentObject _momento = null;  // Momento Pattern 	

	protected virtual object Clone()//override for deep copy
	{
		return this.MemberwiseClone();//shallow copy
	}
	public void SaveState()  // Momento Pattern
	{
		_momento = (PersistentObject)Clone();
	}
	public PersistentObject RestoreState()  // Momento Pattern
	{
		return  _momento;
	}	

}

class Employee : PersistentObject
{
	private string _strDepartment;

	public void SetDepartment(string @strDept)
	{
		_strDepartment = @strDept;
	}	
	public string GetDepartment()
	{
		return _strDepartment;
	}
	public Employee() { }
}

class Secretary : Employee
{
	private int _intLanguages = 1;
	public void SetLanguages(int @intLanguages) {
		_intLanguages = @intLanguages;
	}	
	public int GetLanguages()
	{
		return _intLanguages;
	}	
	public Secretary() { }	
}

class Start
{
	public static void Main()
	{
		Secretary s = new Secretary();
		s.SetTimeOut(60);
		s.SetDepartment("Marketing");
		s.SetLanguages(3);

 		s.SaveState();		
		s.SetTimeOut(120);
		s.SetDepartment("Management");
		s.SetLanguages(4);

		s=(Secretary)s.RestoreState();	
		Console.WriteLine(s.GetTimeOut());
		Console.WriteLine(s.GetDepartment());
		Console.WriteLine(s.GetLanguages());
	}
}

 

    คุณ Atipon ขอให้ผมวิจารณ์ Code (ขออภัยคุณ Atipon ที่ไม่ได้ขออนุญาตก่อน) ซึ่ง Code นี้สะดวกมากครับ เพราะ โปรแกรมเมอร์ที่เขียน Business Class นั้นไม่ต้องทำอะไรให้ Momento เลย เพียงแค่ Inherit ไปเท่านั้น ก็จะได้คุณสมบัติ Momento โดยอัตโนมัติ หลักการที่ Code นี้ใช้ก็คือ เมื่อเรียก Method SaveState() นั้น ตัวของ Object แอบเรียกใช้ MemberwiseClone() ซึ่งเป็น Method ที่มีมากับทุก Object อยู่แล้ว มันจะทำการคัดลอกหน่วยความจำ bit ต่อ bit มาเป็นอีก copy หนึ่ง ดังนั้นในส่วน SaveState() จึงตัดความยุ่งยากออกจากโปรแกรมเมอร์ที่ inherit ไปใช้งานอย่างเด็ดขาด แต่ปัญหาจะอยู่ในตอน RestoreState() ครับ ซึ่งคุณ Atipon ใช้วิธีให้ Pointer ชี้ไปยัง Object ตัวใหม่ (ดู Code ที่ผมลงสีแดงครับ) ซึ่งจุดนี้ถือว่าเป็นจุดอ่อน เพราะ Object ใดๆ อาจจะไม่ได้มีตัวชี้เพียงตัวเดียวครับ มันอาจจะมีหลายตัว ซึ่งถ้าใช้วิธีข้างบน เราต้อง Assign Pointer ใหม่ทุกตัว ถ้าทำไม่ครบก็อาจจะเกิดปัญหาได้ ซึ่งผมเองก็คิดถึงการทำ RollBack ซึ่งเราต้อง Array ตัวหนึ่งไปชี้ Object ทุกตัว เมื่อ RollBack เราจะวน Array แล้วเรียก RestoreState() ซึ่งถ้าทำอย่างนั้น Code นี้ก็ไม่รองรับครับ ดังนั้น เราต้องไม่ใช้

        s = s.RestoreState()

 เราต้องหาวิธีทำให้เป็น

        s.RestoreState()

 

    ผมเองก็ไม่ได้สนใจเรื่องนี้อีกจนกระทั่งผมไปอ่านใน Webboard ของ Playground ที่คุณ Woot กับ ตัวป่วนคุยกัน มีหลุดคำว่า MetaClass ออกมา ทำให้ผมได้แนวคิดครับ มันก็คือเรื่องของ Reflection นั่นเอง ผมก็ได้ Code ดังนี้ครับ

public void RestoreState()
{
	Type p = this.GetType();
	object o;
	while (p.Name != "Object") 
	{
		FieldInfo[] fields = p.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
		foreach (FieldInfo field in fields) 
		{
			o = field.GetValue(_momento);
			field.SetValue(this, o);
		}
		p = p.BaseType;
	}
}

    จาก Code นี้ผมเอา Reflection มาช่วย เราต้องดึงเอาค่าตัวแปรทุกค่าออกมาจาก _momento คืนให้กับตัวแปรทุกตัวใน object ปัจจุบัน แต่ความยากมันอยู่ที่ว่าตัวแปรมันมีหลายระดับครับ เพราะมัน Inherit มาได้โดยไม่จำกัดจำนวนชั้น วิธีการก็คือผมต้องเริ่มที่ชั้นล่าสุด กล่าวคือ inherit มาล่าสุดนั่นเอง ซึ่งตัวแปร this จะหมายถึงชนิดตัวแปรระดับสุดท้าย(ล่าสุด) เสมอ ผมจึงเก็บชนิดตัวแปรมันออกมา ดัง Code นี้ครับ

Type p = this.GetType();

    จากนั้นผมจะแกะเอา Fields ของมันออกมา โดยเอาเฉพาะ Field ที่เป็น instance ทั้งหมด โดยใช้คำสั่งนี้ครับ

FieldInfo[] fields = p.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);

    จากนั้นผมก็ Assign ตัวแปรจาก _momento มาเก็บไว้ทีละตัวดังนี้ครับ

o = field.GetValue(_momento);
field.SetValue(this, o);

    แต่การทำเช่นนี้มันจะทำได้เฉพาะในระดับเดียวกันเท่านั้น การที่จะไประดับที่เป็นแม่ของมัน (base class) ก็ต้องใช้คำสั่งนี้ครับ

p = p.BaseType;

เราก็จะวนไปเรื่อยๆ จนกระทั้ง Base Class เป็น Object นั่นหมายถึงว่าเราขึ้นมาจุดสูงสุดแล้วครับ วิธีการทดสอบ ดังนี้ครับ

while (p.Name != "Object") 

    ผมว่า Code นี้อยู่ในเกณฑ์ดีแล้ว อาศัยในส่วน SaveState() ของคุณ Atipon รวมกับ Reflection ในส่วน RestoreState() ทำให้โปรแกรมเมอร์ที่ Inherit ไปใช้ ไม่ต้องเขียน Code Overhead เพื่อรองรับ Momento เลยครับ

18/FEB/02