假設情境
現在假如公司要你做一個公司內部的薪資管理系統,根據職位的不同,每月的工資自然不一樣,經理一月10000加上分紅1000,技術人員一月5000加上200的餐補,客服一月3000,現在要是由你來做,你會怎麼設計?代碼無錯便是優已經不適用了
閱讀目錄
一:大部分人的寫法v1.0
二:第一次改版後的代碼v1.1
三:第二次改版後的代碼v1.2
四:第三次改版後的代碼v1.3
五:UML類圖解析
六:總結
七:思考
一:大部分人的寫法v1.0
這樣的寫法會帶來一個問題?什麼問題呢?複用性的問題
假如現在你接了個私活,別的公司讓你也寫個公司內部的薪資計算系統,你說那還不簡單,把代碼複製過去就行啊,有人說過初級程式員的工作就是Ctrl+C和Ctrl+V,這其實是非常不好的編碼習慣,因為當你的代碼中重複的代碼多到一定程度的時候,維護起來就是一場災難,越大的系統這種方式帶來的問題越嚴重,編程有一個原則就是儘可能的想辦法避免重複,再說了比如客戶要求你給它們公司做的內部薪資計算系統是個Web版的,或者是個WindowsApplication版的,那麼你下面的代碼就廢掉了,想想看,薪資計算系統哪些是和控制台程式無關的,只和計算有關的,也就是說分出一個類讓計算和顯示分開,也就是要讓業務和介面分開
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace _1_SimpleFactory
{
class Program
{
static void Main(string[] args)
{
try
{
int intSalary = 0; ;//薪水
string strPost = Console.ReadLine();//職位
switch (strPost)
{
case "經理":
intSalary = 10000 + 1000;
break;
case "技術":
intSalary = 5000 + 200;
break;
case "客服":
intSalary = 3000;
break;
}
Console.WriteLine("工資是:" + intSalary);
Console.ReadLine();
}
catch (Exception ex)
{
}
}
}
}
二:第一次改版後的代碼v1.1
為什麼要改版?因為考慮到複用性的問題
要讓商務邏輯和介面邏輯分開,讓它們的耦合度下降,只有分離開才能達到更容易維護和擴充,也就是業務的封裝
我們建立一個類庫,名字叫BusinessLogic,裡面有個類檔案叫Calculate.cs,專門處理根據職位計算工資的,當我們需要做個Web版的話,只需要把介面邏輯檔案Program.cs代碼中的“Console.WriteLine("工資是:" + intSalary);Console.ReadLine();”換成“ASP.NET的Response.Write(("工資是:" + intSalary);Response.End();”就OK了,商務邏輯檔案Calculate.cs是可以複用的,這樣就達到了業務和介面分離了,如果你現在要做一個Windows版的,手機版的,PDA版的,都可以複用這個Calculate類
這次改版後的代碼修複了原始代碼不能複用性的問題,這裡只是用到了物件導向三個特性中的封裝特性,把商務邏輯封裝起來,繼承和多態在這裡還沒用到呢
這樣的寫法又會帶來一個新的問題?缺乏靈活性,靈活性包含兩個方面,一個方面是修改性,一個方面則是擴充性
已經把業務和介面分離了不是很靈活了嗎?好那我們現在打個比方,比方CEO現在要在公司中安排一些親戚進來工作,職位給了個虛頭銜是助理,親戚自然不能虧待,工資是每月6000+1000,你該怎麼設計呢?只要修改Calculate.cs 商務邏輯檔案,在裡面加個分支就行了,問題來了,如果你這樣做,你只是要增加一個職位卻讓“經理”和“技術”以及“客服”參加了編譯,這是糟糕的,你萬一一不小心把case "經理":intSalary = 10000 + 1000;改成了case "經理":intSalary = 1000 + 1000;你們經理該弄死你了,其次你看到“經理”補助1000,我們技術部補助才200,他奶奶的,不行我要提高我們部門的待遇,然後你把case "技術":intSalary = 5000 + 200;改寫成intSalary = 5000 + 800;這樣你們部門每個技術都要比原來多發600元人民幣了,本來是讓你增加一個功能,卻讓原來運行良好的代碼產生了變化,這是非常糟糕的且有巨大風險的,那麼我們下面該怎麼設計呢?
1:Calculate.cs 商務邏輯檔案
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BusinessLogic
{
public class Calculate
{
/// <summary>
/// 計算工資
/// </summary>
/// <param name="strPost">職位描述</param>
///<returns>工資</returns>
public static int CalculateSalary(string strPost)
{
int intSalary = 0; ;//薪水
switch (strPost)
{
case "經理":
intSalary = 10000 + 1000;
break;
case "技術":
intSalary = 5000 + 200;
break;
case "客服":
intSalary = 3000;
break;
}
return intSalary;
}
}
}
2:Program.cs 介面邏輯檔案
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BusinessLogic;
namespace _1_SimpleFactory
{
class Program
{
static void Main(string[] args)
{
try
{
int intSalary = Calculate.CalculateSalary(Console.ReadLine());
Console.WriteLine("工資是:" + intSalary);
Console.ReadLine();
}
catch (Exception ex)
{
}
}
}
}
工程架構圖
三:第二次改版後的代碼v1.2
為什麼要再次改版呢?因為考慮到靈活性的問題
我們要讓“經理”和“技術”以及“客服”運算類分開,修改其中一個類不影響其他的類,增加“助理”等運算類也不影響其他的代碼
首先還是在名字叫BusinessLogic的類庫裡面有個類檔案叫Calculate.cs的計算工資類,裡面有個虛方法CalculateSalary(),用於計算工資的,然後我們把“經理”和“技術”及“客服”和“助理”寫成了計算工資類的子類,繼承後,重寫了CalculateSalary()方法,這樣要修改任何一個職位的工資,就不需要提供其他職位的代碼了,其次要增加一個職位,只需要建立一個類繼承計算工資類,重寫CalculateSalary()方法就行了,也不用提供其他職位的代碼了
這次改版後的代碼修複了第一版代碼不具有靈活性的問題
這樣寫又會帶來一個新的問題?什麼問題呢?就是如何讓用戶端知道我想用哪個計算工資類呢?我們是用CalculateJinLi.cs這個經理計算工資類,還是用CalculateJiShu.cs這個技術計算工資類呢?
1:Calculate.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BusinessLogic
{
/// <summary>
/// 計算工資抽象類別,這個抽象不是指把它定義為抽象類別,而是它存在的意義是為了抽象
/// </summary>
public class Calculate
{
/// <summary>
/// 計算工資
/// </summary>
///<returns>工資</returns>
public virtual int CalculateSalary()
{
int intSalary = 0; ;//薪水
return intSalary;
}
}
}
2:CalculateJinLi.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BusinessLogic
{
/// <summary>
/// 計算經理工資的類,繼承計算工資類
/// </summary>
public class CalculateJinLi:Calculate
{
public override int CalculateSalary()
{
int intSalary = 10000 + 1000 ;//薪水
return intSalary;
}
}
}
3:CalculateJiShu.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BusinessLogic
{
/// <summary>
/// 計算技術工資的類,繼承計算工資類
/// </summary>
public class CalculateJiShu:Calculate
{
public override int CalculateSalary()
{
int intSalary = 5000 + 200;
return intSalary;
}
}
}
4:CalculateKeFu.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BusinessLogic
{
/// <summary>
/// 計算客服工資的類,繼承計算工資類
/// </summary>
public class CalculateKeFu:Calculate
{
public override int CalculateSalary()
{
int intSalary = 3000;
return intSalary;
}
}
}
5:CalculateZhuLi.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BusinessLogic
{
/// <summary>
/// 計算助理工資的類,繼承計算工資類
/// </summary>
public class CalculateZhuLi:Calculate
{
public override int CalculateSalary()
{
int intSalary = 6000 + 1000;
return intSalary;
}
}
}
工程架構圖
四:第三次改版後的代碼v1.3
第二次代碼改版後遇到的問題就是如何去執行個體化對象的問題,到底要執行個體化誰,將來會不會增加執行個體化的對象,比如增加“助理”職位的計算工資,這是很容易變化的地方,以後保不準會增加“銷售”這個職位的計算工資類等,應該考慮一個單獨的類來做這個創造執行個體的過程,這就是工廠
在第二版代碼上,我們增加CalculateFactory這個類庫,裡面有個CalculateFactory.cs類檔案,類檔案中有個CreateCalculate()方法,根據輸入的職位,工廠執行個體化出合適的對象,通過多態返回父類的方式實現計算工資的結果
1:CalculateFactory.cs 工廠類檔案
using System.Linq;
using System.Text;
using BusinessLogic;
namespace CalculateFactory
{
/// <summary>
/// 計算工資工廠類
/// </summary>
public class CalculateFactory
{
/// <summary>
/// 根據傳入的職位創造對應的執行個體
/// </summary>
/// <param name="strPost">職位</param>
/// <returns>Calculate抽象類別</returns>
public static Calculate CreateCalculate(string strPost)
{
Calculate calulate = null;
switch (strPost)
{
case "經理":
calulate = new CalculateJinLi();
break;
case "技術":
calulate = new CalculateJiShu();
break;
case "客服":
calulate = new CalculateKeFu();
break;
case "助理":
calulate = new CalculateZhuLi();
break;
}
return calulate;
}
}
}
2:Program.cs 用戶端檔案
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BusinessLogic;
using CalculateFactory;
namespace _1_SimpleFactory
{
class Program
{
static void Main(string[] args)
{
Calculate calculate = null;
//由工廠根據參數決定要執行個體化哪個對象
calculate = CalculateFactory.CalculateFactory.CreateCalculate("經理");
int intResult = calculate.CalculateSalary();
Console.WriteLine("經理工資為:" + intResult);
calculate = CalculateFactory.CalculateFactory.CreateCalculate("技術");
intResult = calculate.CalculateSalary();
Console.WriteLine("技術工資為:" + intResult);
calculate = CalculateFactory.CalculateFactory.CreateCalculate("客服");
intResult = calculate.CalculateSalary();
Console.WriteLine("客服工資為:" + intResult);
calculate = CalculateFactory.CalculateFactory.CreateCalculate("助理");
intResult = calculate.CalculateSalary();
Console.WriteLine("助理工資為:" + intResult);
Console.ReadLine();
}
}
}
運行
工程架構圖
五:UML類圖解析
整合模組化語言UML(是Unified Modeling Language)的縮寫,是用來對軟體密集系統進行可視化建模的一種語言,UML為物件導向開發系統的產品進行說明,可視化,和編製文檔的一種標準語言
類圖分為三層,第一層顯示類的名稱,如果是抽象類別,則用斜體表示,第二層是類的特性,通常是欄位和屬性,第三層是類的操作,通常是方法和行為,前面的符號“+”,表示public,”-“表示private,“#”表示protected
六:總結
這樣我們就完成了一個具有複用性,靈活性(可修改,可擴充)的公司內部薪資管理系統,我們同時也看到這樣一個小小的公司內部薪資系統,都用到了封裝,繼承,多態
七:思考
如果我們有一天我們需要更改經理的工資怎麼辦?我們只需要改CalculateJinLi.cs裡的代碼就行了,如果我們需要增加“銷售”職位的工資演算法怎麼辦?我們只需要增加相應的子類就行了,還要去計算工資工廠在switch裡增加分支就行了,那我們要去修改介面怎麼辦?比如我們換成Web版的,那就去改介面呀,跟運算毫無關係
這個只是24種設計模式中最簡單的“簡單對象訪問模式”,其他很多設計模式有的很複雜,由於平時工作很忙,沒有及時更新請大家諒解,請關注我的部落格