迈向架构设计师之路系列—简单对象访问模式(一)
假设场景
现在假如公司要你做一个公司内部的薪资管理系统,根据职位的不同,每月的工资自然不一样,经理一月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种设计模式中最简单的“简单对象访问模式”,其他很多设计模式有的很复杂,由于平时工作很忙,没有及时更新请大家谅解,请关注我的博客