The Framework Designing(2)- Writing Extensible Application

前一篇中,我們討論了Framework的觀念及設計時的注意事項,不過你我都明白,僅靠這些簡短的敘述,是不可能設計出Framework,更不用談實作部份了。

相對於撰寫應用程式,設計Framework通常需要更多的軟體架構及經驗,在看軟體架構的高度也與一般撰寫應用程式不同,通常設計Framework時,架構師必須要以很高的高度來看整個系統架構,然後逐步地往細節走,一開始,是看整棟大樓的位置,周邊,接著細看到每個房間的佈局,越往細節,所造出的限制就越多,這就是設計Framework的基礎觀念之一。

The Framework Designing(2)- Writing Extensible Application

 

 

/黃忠成

 

 

Designing Framework ??

 

  前一篇中,我們討論了Framework的觀念及設計時的注意事項,不過你我都明白,僅靠這些簡短的敘述,是不可能設計出Framework,更不用談實作部份了。

  相對於撰寫應用程式,設計Framework通常需要更多的軟體架構及經驗,在看軟體架構的高度也與一般撰寫應用程式不同,通常設計Framework時,架構師必須要以很高的高度來看整個系統架構,

然後逐步地往細節走,一開始,是看整棟大樓的位置,周邊,接著細看到每個房間的佈局,越往細節,所造出的限制就越多,這就是設計Framework的基礎觀念之一。

  有經驗的架構師,在設計Framework或是應用程式架構時,會直接跳過許多嘗試階段,以網狀方式採用多種Design Patterns來設計,但對於較沒有經驗的設計師來說,沒有經過這些嘗試階段,

說實話很難理解為何架構師要這樣設計,明明很簡單的事,卻要繞幾個圈,明明可以用一個專案(Project)做完的事,偏偏要搞出4-5個專案,光是搞清楚彼此間的關係就夠頭大的了,何況還要在這個架構下實作。

  所以架構師很容易遭遇來自於程式設計師的質疑,例如這樣做會影響效率,這樣做很麻煩,這樣做很奇怪等等,殊不知架構師這樣的設計,是在為以後軟體架構變動留下後路,避免頻繁的重造輪子之事發生。

  設計Framework或是可延展的應用程式時,必須明白一件事,那就是可延展與高效率是無法畫上等號的,就像你以Parameter方式來對SQL Server新增1000筆資料,比起把1000次的INSERT放在一個命令(SqlCommand)

執行來得慢,但這不該是捨Parameter而選用後者的原因是吧?

 

 

從一個ASP.NET應用程式開始

 

  在開始進入設計Framework正題前,得先學會如何設計一個具延展性的應用程式,所謂延展性,就是指當需求變動時,應用程式能以最小的改動及最低的影響適應變動,當然! 前提是這個需求變動是當初可預期的。

本文中以一個簡單的ASP.NET應用程式開始,這個程式很簡單,透過SqlDataSource連結SQL Server,經由FormView讓使用者新增、修改即刪除資料。

 

Default.aspx

<%@Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="FirstDemo.Default" %>

 

<!DOCTYPEhtml PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

 

<htmlxmlns="http://www.w3.org/1999/xhtml">

<headrunat="server">

    <title></title>

</head>

<body>

    <form id="form1" runat="server">

   

    <asp:SqlDataSource ID="SqlDataSource1" runat="server"

        ConnectionString="<%$ ConnectionStrings:NorthwindConnectionString %>"

        DeleteCommand="DELETE FROM [Customers] WHERE [CustomerID] = @CustomerID"

        InsertCommand="INSERT INTO [Customers] ([CustomerID], [CompanyName], [ContactName], [ContactTitle], [Address], [City], [Region], [PostalCode], [Country], [Phone], [Fax], [NOTES]) VALUES (@CustomerID, @CompanyName, @ContactName, @ContactTitle, @Address, @City, @Region, @PostalCode, @Country, @Phone, @Fax, @NOTES)"

        SelectCommand="SELECT * FROM [Customers]"

        UpdateCommand="UPDATE [Customers] SET [CompanyName] = @CompanyName, [ContactName] = @ContactName, [ContactTitle] = @ContactTitle, [Address] = @Address, [City] = @City, [Region] = @Region, [PostalCode] = @PostalCode, [Country] = @Country, [Phone] = @Phone, [Fax] = @Fax, [NOTES] = @NOTES WHERE [CustomerID] = @CustomerID">

       ……………

    </asp:SqlDataSource>

    <asp:FormView ID="FormView1" runat="server" AllowPaging="True"

        BackColor="White" BorderColor="#999999" BorderStyle="None" BorderWidth="1px"

        CellPadding="3" DataKeyNames="CustomerID" DataSourceID="SqlDataSource1"

        GridLines="Vertical">

        <EditItemTemplate>

            CustomerID:

            <asp:Label ID="CustomerIDLabel1" runat="server"

                Text='<%# Eval("CustomerID") %>' />

            <br />

            CompanyName:

            <asp:TextBox ID="CompanyNameTextBox" runat="server"

                Text='<%# Bind("CompanyName") %>' />

            <br />

            ContactName:

            <asp:TextBox ID="ContactNameTextBox" runat="server"

                Text='<%# Bind("ContactName") %>' />

            <br />

            ContactTitle:

            <asp:TextBox ID="ContactTitleTextBox" runat="server"

                Text='<%# Bind("ContactTitle") %>' />

            <br />

            Address:

            <asp:TextBox ID="AddressTextBox" runat="server" Text='<%# Bind("Address") %>' />

            <br />

            City:

            <asp:TextBox ID="CityTextBox" runat="server" Text='<%# Bind("City") %>' />

            <br />

            Region:

            <asp:TextBox ID="RegionTextBox" runat="server" Text='<%# Bind("Region") %>' />

            <br />

            PostalCode:

            <asp:TextBox ID="PostalCodeTextBox" runat="server"

                Text='<%# Bind("PostalCode") %>' />

            <br />

            Country:

            <asp:TextBox ID="CountryTextBox" runat="server" Text='<%# Bind("Country") %>' />

            <br />

            Phone:

            <asp:TextBox ID="PhoneTextBox" runat="server" Text='<%# Bind("Phone") %>' />

            <br />

            Fax:

            <asp:TextBox ID="FaxTextBox" runat="server" Text='<%# Bind("Fax") %>' />

            <br />

            NOTES:

            <asp:TextBox ID="NOTESTextBox" runat="server" Text='<%# Bind("NOTES") %>' />

            <br />

            <asp:LinkButton ID="UpdateButton" runat="server" CausesValidation="True"

                CommandName="Update" Text="Update" />

            &nbsp;<asp:LinkButton ID="UpdateCancelButton" runat="server"

                CausesValidation="False" CommandName="Cancel" Text="Cancel" />

        </EditItemTemplate>

       ………….

    </asp:FormView>   

    <div>   

    </div>

    </form>

</body>

</html>

 

就想達到的目的而言,這個程式並沒有太大的問題,使用者可以透過這個程式來編修Customers資料表中的資料,也能新增新的客戶。

但若細細檢視這個應用程式,會發現其有以下的缺點:

 

  1. 使用SqlDataSource,所以綁死在SQL Server
  2. 沒有考慮延展性,所以當客戶要求新增資料時,如果CompanyName是空白,就直接以CustomerID寫入,未來只能依賴Trigger。
  3. 沒有考慮到Validation的延展機制,例如假設客戶要求CompanyName不能與CustomerID相同,那麼也只能夠依賴Trigger處理。
  4. ………………………………..

 

第一次嘗試挖洞遊戲

 

  個人常比喻,設計一個具延展性的應用程式或Framework,就像玩挖洞遊戲一樣,首先得先預想完成後的成品,然後在這個成品中可能會被延展的部位挖上幾個洞,讓未來延展時有洞可鑽。

  以前例而言,第一點可以使用其它現成的Framework,例如ADO.NET Entity Framework來解決,如非必要,實在不需勞駕自己來設計(雖說如此,但之後的文章會討論一個簡單的,可適用於多種資料庫的方法),

因此先對2、3點解決,這個問題出現的主因是未預想到客戶未來可能提出的需求變更,一旦預知其可能發生之後,便能在儲存資料的部分挖上一個洞,提供未來的延展性。

  那洞該挖在哪呢?2、3點都是發生在資料儲存前,以前例而言,只要掛載FormView的Inserting及Updating事件即可,但問題在於現今無法準確的預知客戶未來只需要預設CompanyName或是避免

CustomerID與CompanyName重複的問題,他們有可能會有更複雜的邏輯運算,所以無法直接以上述需求寫死在程式中,最好的辦法是,在FormView的Inserting、Updating事件中動態的載入一個Assembly,

然後動態的建立物件,接著呼叫其特定的成員函式,也就是說,將驗證儲存的動作由主程式中抽離。

圖1

在圖1中加入了一個FormViewExt的類別,這個類別負責由web.config中的設定來讀取指定的Assembly,並掛載事件到指定的FormView中的Inserting、Updating。

 

FormViewExt.cs


using System;

using System.Collections.Generic;

using System.Linq;

using System.Web;

using System.Web.UI.WebControls;

using System.Configuration;

using System.Reflection;


namespace CreateExt_Step1

{

    public class FormViewExt

    {

        private object _instance = null;

        public void Initialize(string key, FormView view)

        {           

            if (!string.IsNullOrEmpty(ConfigurationManager.AppSettings[key]))

            {

                string[] partAssem = ConfigurationManager.AppSettings[key].Split(',');

                Assembly assem = Assembly.Load(partAssem[1]);

                if (assem == null)

                    throw new Exception("load assembly :" + partAssem[1] + " Error.");

                Type t = assem.GetType(partAssem[0]);

                if (t == null)

                    throw new Exception("load type :" + partAssem[0] + " Error.");

                _instance = Activator.CreateInstance(t);

                view.ItemInserting +=

                       newFormViewInsertEventHandler(view_ItemInserting);

                view.ItemUpdating += new FormViewUpdateEventHandler(view_ItemUpdating);

            }

        }


        void view_ItemInserting(object sender, FormViewInsertEventArgs e)

        {

            MethodInfo mi = _instance.GetType().GetMethod("BeforeSave",

                               BindingFlags.Instance | BindingFlags.Public);

            if (mi != null)

                mi.Invoke(_instance,new object[]{e.Values});

        }


        void view_ItemUpdating(object sender, FormViewUpdateEventArgs e)

        {

            MethodInfo mi = _instance.GetType().GetMethod("BeforeSave",

                     BindingFlags.Instance | BindingFlags.Public);

            if (mi != null)

                mi.Invoke(_instance, new object[] { e.NewValues });

        }

    }

}

以下為Default.aspx.cs使用FormViewExt的程式碼。

 

Default.aspx.cs


using System;

using System.Collections.Generic;

using System.Linq;

using System.Web;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.Reflection;


namespace CreateExt_Step1

{

    public partial class Default : System.Web.UI.Page

    {

        private FormViewExt ext = new FormViewExt();


        protected void Page_Load(object sender, EventArgs e)

        {

            ext.Initialize("Customers", FormView1);

        }

    }

}

FormViewExt會透過web.config中AppSetting區段所指定的Assembly String來動態讀入Assembly並建立指定的物件,並在Inserting、Updating事件中呼叫其BeforeSave函式,此時會傳入一個IOrderDictionary,

這裡面包含了Inserting、Updating時的更新資料,由指定的物件來決定該怎麼處理,以下為此例的web.config。

 

web.config

<?xmlversion="1.0"?>

 

<!--

  For more information on how to configure your ASP.NET application, please visit

  http://go.microsoft.com/fwlink/?LinkId=169433

  -->

 

<configuration>

    <connectionStrings>

        <addname="NorthwindConnectionString"connectionString="Data Source=127.0.0.1;Initial Catalog=Northwind;.... "

            providerName="System.Data.SqlClient"/>

    </connectionStrings>

  <appSettings>

    <addkey="Customers"value="SaveExt.CustomerExt, SaveExt"/>

  </appSettings>

    <system.web>

        <compilationdebug="true"targetFramework="4.0"/>

    </system.web>

 

</configuration>

預設,當web.config的AppSetting區段有Customers定義時,FormViewExt才會動態載入指定的Assembly及物件,否則就直接略過。

本例中指定動態載入的Assembly為SaveExt,動態建立物件的類別為SaveExt.CustomerExt。

 

SaveExt.cs(in SaveExt Project)


using System;

using System.Collections.Generic;

using System.Collections.Specialized;

using System.Linq;

using System.Text;


namespace SaveExt

{

    public class CustomerExt

    {

        public void BeforeSave(IOrderedDictionary values)

        {

            if (string.IsNullOrEmpty((string)values["CompanyName"]))

                values["CompanyName"] = "Empty in save";

        }

    }

}

注意,SaveExt Project(一個類別庫專案,Class Library)的output目錄必須設在ASP.NET應用程式的bin目錄,也就是說,SaveExt Project所產出的DLL必須放在ASP.NET應用程式的bin目錄。

執行此應用程式,進行任何修改或新增並按下儲存後,會發現當未輸入CompanyName欄位時,其會預設為”Empty In save”。

這個架構給與了此程式初步的延展性,但這作法還不是很恰當,接下來讓我們更精煉化這個想法。

 

 

切出可重用部分-Simple Framework V1

 

 

  在前例中雖做出了延展性,但若仔細思考,會發現FormViewExt中讀取AppSetting區段及動態載入Assembly、建立物件的部分,可以用在很多地方,現在是僅用在FormView上,改天可能GridView

或是其它地方都會有使用這個技巧的需求,所以這個部份可以往上提升,成為Framework的基底類別之一。

圖2

 

TypeLoader.cs(in SimpleFramework Project- a class library)


using System;

using System.Collections.Generic;

using System.Linq;

using System.Web;

using System.Web.UI.WebControls;

using System.Configuration;

using System.Reflection;


namespace SimpleFramework

{

    public static class TypeLoader

    {       

        public static object CreateInstance(string assemblyString, string type)

        {

            Assembly assem = Assembly.Load(assemblyString);

            if (assem == null)

                throw new Exception("load assembly :" + assemblyString + " Error.");

            Type t = assem.GetType(type);

            if (t == null)

                throw new Exception("load type :" + type + " Error.");

            return Activator.CreateInstance(t);

        }


        public static MethodInfo GetMethod(object instance, string methodName)

        {

            return instance.GetType().GetMethod(methodName,

                  BindingFlags.Instance | BindingFlags.Public);

        }

    }

}

FormViewExt.cs(in ASP.NET Project)


using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Web.UI.WebControls;

using System.Configuration;

using System.Reflection;

using SimpleFramework;


namespace CreateExt_Step1

{

    public class FormViewExt

    {

        private object _instance = null;

        public void Initialize(string key, FormView view)

        {

            if (!string.IsNullOrEmpty(ConfigurationManager.AppSettings[key]))

            {

                string[] partAssem = ConfigurationManager.AppSettings[key].Split(',');

                _instance = TypeLoader.CreateInstance(partAssem[1], partAssem[0]);

                view.ItemInserting += new FormViewInsertEventHandler(view_ItemInserting);

                view.ItemUpdating += new FormViewUpdateEventHandler(view_ItemUpdating);

            }

        }


        void view_ItemInserting(object sender, FormViewInsertEventArgs e)

        {

            MethodInfo mi = TypeLoader.GetMethod(_instance, "BeforeSave");

            if (mi != null)

                mi.Invoke(_instance, new object[] { e.Values });

        }


        void view_ItemUpdating(object sender, FormViewUpdateEventArgs e)

        {

            MethodInfo mi = TypeLoader.GetMethod(_instance, "BeforeSave");

            if (mi != null)

                mi.Invoke(_instance, new object[] { e.NewValues });

        }

    }

}

這個步驟主要在於提取未來可共用的部分出來,形成Framework的一部份,圖3為現在方案的結構,注意! ASP.NET Project必須將SimpleFramework加為參考。

圖3

 

 

再次重構,切出更多可重用部分及定義延展規範-Simple Framework V2

 

  前例中還是有一些精煉化的空間存在,首先,我們使用了Reflection來呼叫BeforeSave,這可能會造成未來的困擾,因為沒有規範,如果文件未載明,那麼只要字打錯就掛了,所以較好的做法

是在FormViewExt及被呼叫的類別間建立一個協定,介面是完成這個任務最快的方式。

  另外,FromViewExt也存在被重用的價值,所以一併將其移往SimpleFramework專案中。

圖4

 

IDataExtension.cs(In SimpleFramework Project)


using System;

using System.Collections.Generic;

using System.Collections.Specialized;

using System.Linq;

using System.Text;


namespace SimpleFramework

{

    public interface IDataExtension

    {

        void BeforeSave(IOrderedDictionary values);

    }

}

FormViewExt.cs(in SimpleFramework Project)


using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Web.UI.WebControls;

using System.Configuration;

using System.Reflection;

using SimpleFramework;


namespace SimpleFramework.Web

{

    public class FormViewExt

    {

        private IDataExtension  _instance = null;

        public void Initialize(string key, FormView view)

        {

            if (!string.IsNullOrEmpty(ConfigurationManager.AppSettings[key]))

            {

                string[] partAssem = ConfigurationManager.AppSettings[key].Split(',');

                _instance = TypeLoader.CreateInstance(partAssem[1], partAssem[0],

"SimpleFramework.IDataExtension") as IDataExtension;

                view.ItemInserting += new FormViewInsertEventHandler(view_ItemInserting);

                view.ItemUpdating += new FormViewUpdateEventHandler(view_ItemUpdating);

            }

        }


        void view_ItemInserting(object sender, FormViewInsertEventArgs e)

        {

            _instance.BeforeSave(e.Values);

        }


        void view_ItemUpdating(object sender, FormViewUpdateEventArgs e)

        {

            _instance.BeforeSave(e.NewValues);

        }

    }

}

實作延展的CustomerExt也得依照規範實作IDataExtension介面

 

CustomerExt.cs(in SaveExt Project)


using System;

using System.Collections.Generic;

using System.Collections.Specialized;

using System.Linq;

using System.Text;


namespace SaveExt

{

    public class CustomerExt:SimpleFramework.IDataExtension

    {

        public void BeforeSave(IOrderedDictionary values)

        {

            if (string.IsNullOrEmpty((string)values["CompanyName"]))

                values["CompanyName"] = "Empty in save";

        }

    }

}

圖5為目前的方案結構。

 

 

去除相依性– Simple Framework V3

 

  基本上,Simple Framework V2還算不錯,但其仍有精煉化的空間,因為TypeLoader其實可以用在非ASP.NET的專案上面,例如Windows Form或是WPF,但因為Simple Framework中包含了FormViewExt,

所以當Windows Form或是WPF想使用TypeLoader時,就需要System.Web存在,這雖然不會造成困擾,但許多設計者(包含我)都有種潔癖,沒用到的東西,就不需要載入(當使用於非ASP.NET專案時,FormViewExt是無用的)。

因此,這階段我們進行去除相依性的部分。

圖6

此例中將FormViewExt移往另一個Assembly(Class Library)SimpleFramework.Web中,當需要將TypeLoader用於Windows Form、WPF時,只需要引用SimpleFramework,

而不需要引用多餘且無用的SimpleFramework.Web。

 

FormViewExt.cs(in SimpleFramework.Web)


using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Web.UI.WebControls;

using System.Configuration;

using System.Reflection;

using SimpleFramework;


namespace SimpleFramework.Web

{

    public class FormViewExt

    {

        private IDataExtension _instance = null;

        public void Initialize(string key, FormView view)

        {

            if (!string.IsNullOrEmpty(ConfigurationManager.AppSettings[key]))

            {

                string[] partAssem = ConfigurationManager.AppSettings[key].Split(',');

                _instance = TypeLoader.CreateInstance(partAssem[1], partAssem[0],

"SimpleFramework.IDataExtension") as IDataExtension;

                view.ItemInserting += new FormViewInsertEventHandler(view_ItemInserting);

                view.ItemUpdating += new FormViewUpdateEventHandler(view_ItemUpdating);

            }

        }


        void view_ItemInserting(object sender, FormViewInsertEventArgs e)

        {

            _instance.BeforeSave(e.Values);

        }


        void view_ItemUpdating(object sender, FormViewUpdateEventArgs e)

        {

            _instance.BeforeSave(e.NewValues);

        }

    }

}

圖7為目前方案結構。

圖7

 

真的可重用嗎?延展Simple Framework V3令其可作用於Windows Form

 

  設計是這樣設計,但真的可以沿用在Windows Form上嗎?請看圖8。

圖8

圖8中除了原本的SimpleFramework及SimpleFramework.Web(省略)之外,還多設計了一個SimpleFramework.WinForms,主要在於提供GridView類似FormView的機制,讓設計師可以透過同樣的機制來延展

資料儲存的驗證,注意!我們並未動到原本的SimpleFramework、SimpleFramework.Web,只是單純的加入SimpleFramework.WinForms而已。

 

GridViewExt.cs(in SimpleFramework.WinForms)


using System;

using System.Collections.Generic;

using System.Collections.Specialized;

using System.Linq;

using System.Text;

using System.Windows.Forms;

using System.Configuration;


namespace SimpleFramework.WinForms

{

    public class GridViewExt

    {

        private IDataExtension _instance;

        private DataGridView _view;


        public void Initialize(string key, DataGridView view)

        {

            if (!string.IsNullOrEmpty(ConfigurationManager.AppSettings[key]))

            {

                string[] partAssem = ConfigurationManager.AppSettings[key].Split(',');

                _instance = TypeLoader.CreateInstance(partAssem[1], partAssem[0], "SimpleFramework.IDataExtension") as IDataExtension;

                view.RowValidating +=

newDataGridViewCellCancelEventHandler(view_RowValidating);

                _view = view;

            }

        }


        void view_RowValidating(object sender, DataGridViewCellCancelEventArgs e)

        {

            OrderedDictionary values = new OrderedDictionary();

            foreach (DataGridViewCell item in _view.Rows[e.RowIndex].Cells)

            {

                if(item.Value is DBNull)

                    values.Add(_view.Columns[item.ColumnIndex].DataPropertyName, null);

                else

                    values.Add(_view.Columns[item.ColumnIndex].DataPropertyName,

item.Value);

            }

            _instance.BeforeSave(values);

            foreach (DataGridViewCell item in _view.Rows[e.RowIndex].Cells)

                item.Value = values[_view.Columns[item.ColumnIndex].DataPropertyName];

        }

    }

}

接著只要建立一個WinForm專案,並放置GridView及BindingSource後,透過同樣的流程即可使用GridViewExt。

 

Form1.cs(in Windows Form Project)


using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Text;

using System.Windows.Forms;

using SimpleFramework.WinForms;


namespace WindowsFormsApplication1

{

    public partial class Form1 : Form

    {

        private GridViewExt _ext = new GridViewExt();


        public Form1()

        {

            InitializeComponent();

        }


        private void Form1_Load(object sender, EventArgs e)

        {

            // TODO: This line of code loads data into the 'dataSet1.Customers' table. You can move, or remove it, as needed.

            this.customersTableAdapter.Fill(this.dataSet1.Customers);

            _ext.Initialize("Customers", dataGridView1);

        }


        private void button1_Click(object sender, EventArgs e)

        {

            customersTableAdapter.Update(dataSet1.Customers);

            dataSet1.Customers.AcceptChanges();

        }

    }

}

app.config

<?xmlversion="1.0"encoding="utf-8"?>

<configuration>

    <configSections>

    </configSections>

    <connectionStrings>

        <addname="WindowsFormsApplication1.Properties.Settings.NorthwindConnectionString"

            connectionString="Data Source=127.0.0.1;Initial Catalog=Northwind…"

            providerName="System.Data.SqlClient"/>

    </connectionStrings>

  <appSettings>

    <addkey="Customers"value="SaveExt.CustomerExt, SaveExt"/>

  </appSettings>

</configuration>

此程式的執行效果與ASP.NET差不多,差別只在於一個是ASP.NET、一個是Windows Form。

另外,此例證明了我們可以重用SimpleFramework在ASP.NET及Windows Form,且也可以透過SimpleFramework原本設計好的延展性來加入新功能,還有值得一提的是,同時也重用了SaveExt Project。

圖9

 

正名為類別及延展規範取個樣式(Patterns)名稱

 

  好了,現在讀者們應該了解,為何有些應用程式開起來會有一堆Project的原因了,也了解了如何打造具延展性的應用程式,雖然還很陽春,但Simple Framework已經有一個Framework的雛型,未來的文章中

將繼續來精煉並加入更多功能。

  文末,必須一提的是,Simple Framework其實使用了一個設計樣式(Design Pattern),名為Handler,Handler指的是當事件發生時的處理者,IDataExtension就是一個Handler,所以,建議讀者們可以將IDataExtension

改名為IDataHandler,或是IDataProcessHander,更貼近於軟體工程中的用語。

圖10

 

設計一個Framework或具延展性的應用程式這麼麻煩嗎?

 

  恩,的確,過程很麻煩,但當你了解後,便可以在腦中走過前面的幾個嘗試,直接畫出圖10或圖6來,並直接進入Simple Framework V3或是V10的設計及實作,這是許多架構師本身就具備的能力,

也是應該要具備的能力。

 

是最終版本嗎? Simple Framework V3

 

   不是,Simple Framework V3還稱不上是最終版本,因為以目的而言,這個延展點(Handler)應該放在資料層,而不是UI層,只是當這麼做時,將會引導出Data Access Layout的設計,讓本文變得

相對複雜及難懂,所以我特意讓其停留在UI層,未來的文章將持續改良。

   不過,Simple Framework V3從UI延展的設計概念還是有用的,因為即使將延展點移往Data Access Layout,使用的手法還是一樣的。

 

 

範例下載:

http://code6421.sg1002.myweb.hinet.net/Framework/Arch_1.zip