zoukankan      html  css  js  c++  java
  • 委托,语言级别的设计模式

    我们有个发票类,需要提供一个打印的方法,客户告诉我们,这个订单要提供多种打印的样式,那么我们一般会这样设计
     1    public enum CommercialInvoiceMode//商业发票样式
     2    {
     3        Duplicate, //一式两份 
     4        Triplicate, //一式三份 
     5        Quadruplicate, //一式四份 
     6    }

     7
     8    /// <summary>
     9    ///  描述发票数据
    10    /// </summary>

    11    public class Invoice
    12    {
    13        /// <summary>
    14        /// 打印发票
    15        /// </summary>
    16        /// <param name="commercialInvoiceMode">商业发票样式</param>

    17        public void PrintInvoice(CommercialInvoiceMode commercialInvoiceMode)
    18        {
    19        }

    20    }
    这样的设计看似没有什么问题,用一个枚举可以描述发票不同的打印模式,仅提供一个PrintInvoice方法就可以实现对多种发票打印样式的处理,是不是自我感觉很良好啊?
    不过这样的设计有些致命的缺点:
    具体的实现打印的代码结构大致如下
     1    /// <summary>
     2    ///  描述发票数据
     3    /// </summary>

     4    public class Invoice
     5    {
     6        /// <summary>
     7        /// 打印发票
     8        /// </summary>
     9        /// <param name="commercialInvoiceMode">商业发票样式</param>

    10        public void PrintInvoice(CommercialInvoiceMode commercialInvoiceMode)
    11        {
    12            switch(commercialInvoiceMode)
    13            {
    14                case CommercialInvoiceMode.Decuplicate:
    15                    break;
    16                case CommercialInvoiceMode.Triplicate:
    17                    break;
    18                case CommercialInvoiceMode.Quadruplicate:
    19                    break;
    20            }

    21        }

    22    }
    如果你对以上的代码没有什么歧义,那就说明你对类的开闭原则不是很了解。
    为什么这样的设计不好呢?
    比如用户要求有了新的打印样式
    我们要修改枚举
     1    public enum CommercialInvoiceMode//商业发票样式
     2    {
     3        Duplicate, //一式两份 
     4        Triplicate, //一式三份 
     5        Quadruplicate, //一式四份 
     6        Quintuplicate, //一式五份 
     7        Sextuplicate,//一式六份 
     8        Septuplicate,//一式七份 
     9        Octuplicate,//一式八份 
    10       Nonuplicate, //一式九份 
    11       Decuplicate,//一式十份
    12    }

    修改了枚举,就必须要修改PrintInvoice方法中的switch代码,因为多了发票样式,就必须重新的修改类。重新的修改枚举,典型的破坏类的封装。
    如果,你还是不同意这样的设计破坏了封装,那我们再考虑一个场景:要是以上的打印样式分别是两个客户提出的,难道你要两个枚举?
     1    public enum CommercialInvoiceModeA//商业发票样式
     2    {
     3        Septuplicate,//一式七份 
     4        Octuplicate,//一式八份 
     5        Nonuplicate, //一式九份 
     6        Decuplicate,//一式十份
     7    }

     8
     9    public enum CommercialInvoiceModeB//商业发票样式
    10    {
    11        Sextuplicate,//一式六份 
    12        Septuplicate,//一式七份 
    13        Octuplicate,//一式八份 
    14        Nonuplicate, //一式九份 
    15        Decuplicate,//一式十份
    16    }

    如果有这两个枚举,那PrintInvoice方法马上要完全改变,这个类就不能通用了。必须为不同的客户提供不同的Invoice类,假设Invoice的数据和行为都一样,那就是说必须为了InvoiceStyle而实现不同的类。
    而这些InvoiceStyleA和InvoiceStyleB类仅仅是打印的样式不同,其他的数据和行为都一样,所以这样的设计不完全合理。
    我们需要一个Invoice类,该类的PrintInvoice能在不改变Invoice类的实现情况下,可以灵活的处理打印。
    我们先设计一个发票打印类:
     1    public class PrintInvoice
     2    {
     3        //一式两份
     4        public static void DuplicateStyle(Invoice data)
     5        {
     6        }

     7        //一式三份
     8        public static void TriplicateStyle(Invoice data)
     9        {
    10        }

    11        //一式四份
    12        public static void QuadruplicateStyle(Invoice data)
    13        {
    14        }

    15    }

    这个打印类,为每种打印提供了具体的实现,将来有了新的打印样式,仅仅是增加新的方法(可以直接为类增加或继承后增加),没有破坏类的开闭原则。
    那这个类可以帮我们做什么呢?
    我们观察一下,这几个方法的外观其实是一致的:同样的返回值,同样的参数列表。
    让你想到了什么?接口?恩,是的,想想如果用接口的知识,我们现在能做些什么呢?
    我们一定会设计如下的类:
     1    public interface IPrintInvoice
     2    {
     3        void PrintInvoice(Invoice data);
     4    }

     5
     6    public class DuplicateStyle : IPrintInvoice
     7    {
     8        public void PrintInvoice(Invoice data)
     9        
    10        
    11        }

    12    }

    13
    14
    15    public class TriplicateStyle : IPrintInvoice
    16    {
    17        public void PrintInvoice(Invoice data)
    18        {
    19
    20        }

    21    }

    22
    23
    24    public class QuadruplicateStyle : IPrintInvoice
    25    {
    26        public void PrintInvoice(Invoice data)
    27        {
    28
    29        }

    30    }

    然后我们的Invoice类如下设计:
    1    public class Invoice
    2    {
    3        public void PrintInvoice(IPrintInvoice printStyle)
    4        {
    5            printStyle.PrintInvoice(this);
    6        }

    7    }
    这个Invoice类设计的就非常的漂亮,符合开闭原则了。不过,不足的就是要写很多IPrintInvoice的实现类,每个类就一个PrintInvoice方法,这样的话,类实在太多了。不方便管理。
    所以我们还是在我们先前的PrintInvoice类打主意。
    C#提供了一种特殊的引用类型:委托。这个类型可以把我们的方法(函数)包装为独立的对象。然后就可以把这个包装后的方法(委托)当参数用。这个参数具有被包装函数的所有特征:有返回值和参数。就是说该包装后的对象其实就是一个方法(函数)。
    我们来看一下,委托的声明。
    1public delegate void PrintStyle(Invoice data);
    就一行,没有{}的,在类外面声明(也就是说委托的级别和类、接口的级别是一样的)。
    这个委托描述了,他可以帮助返回为void,参数为Invoice类型的函数,具体函数的名字它不管,它只要返回值和参数列表符合他的包装要求就可以了。
    我们修改Invoice的PrintInvoice方法
     1    /// <summary>
     2    ///  描述发票数据
     3    /// </summary>

     4    public class Invoice
     5    {
     6
     7        public void PrintInvoice(PrintStyle printStyle)
     8        {
     9            printStyle(this);
    10        }

    11    }
    看看,第9行是不是用起来很接近接口的用法啊?参数PrintStyle方法传入的就是外观和delegate void PrintStyle(Invoice data)一致的函数,用这个PrintStyle就是调用一个函数。
    看看具体的调用
    1            Invoice invoice = new Invoice();
    2            invoice.PrintInvoice(new PrintStyle(PrintInvoice.QuadruplicateStyle));
    上面的代码就是把PrintInvoice的QuadruplicateStyle方法传递给了Invoice类的PrintInvoice,那么前端代码的第9行其实就是调用PrintInvoice的QuadruplicateStyle方法。
    1            Invoice invoice = new Invoice();
    2            invoice.PrintInvoice(new PrintStyle(PrintInvoice.QuadruplicateStyle));
    3            PrintStyle printStyle = new PrintStyle(PrintInvoice.TriplicateStyle);
    4            invoice.PrintInvoice(printStyle);
    5            printStyle = new PrintStyle(PrintInvoice.DuplicateStyle);
    6            invoice.PrintInvoice(printStyle);
    委托再次提醒我们在类设计时的一个真理:直接的是最不稳定的,间接的才是最稳定的
  • 相关阅读:
    六、order set结构及命令详解
    五、set结构及命令详解
    四、redis的link结构及命令详解
    三、redis对字符串类型的操作
    二、redis对于key的操作命令
    一、redis的特点以及安装使用
    Mysql5.7以上版本group by报错问题
    1.4 java高并发程序设计-无锁
    sysbench工具和mysql的基准测试
    sqli-labs(29-31关)
  • 原文地址:https://www.cnblogs.com/shyleoking/p/654563.html
Copyright © 2011-2022 走看看