• 委托,语言级别的设计模式


    我们有个发票类,需要提供一个打印的方法,客户告诉我们,这个订单要提供多种打印的样式,那么我们一般会这样设计
     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);
    委托再次提醒我们在类设计时的一个真理:直接的是最不稳定的,间接的才是最稳定的
  • 相关阅读:
    第三天 moyax
    mkfs.ext3 option
    write file to stroage trigger kernel warning
    download fomat install rootfs script
    custom usb-seriel udev relus for compatible usb-seriel devices using kermit
    Wifi Troughput Test using iperf
    learning uboot switch to standby system using button
    learning uboot support web http function in qca4531 cpu
    learngin uboot design parameter recovery mechanism
    learning uboot auto switch to stanbdy system in qca4531 cpu
  • 原文地址:https://www.cnblogs.com/shyleoking/p/654563.html
Copyright © 2020-2023  润新知