迈向架构设计师之路系列—简单对象访问模式(一)

  假设场景

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

记录,成为更好的自己
原文地址:https://www.cnblogs.com/menglin2010/p/2343020.html