zoukankan      html  css  js  c++  java
  • 艾伟_转载:把委托说透(1):开始委托之旅 委托与接口 狼人:

    委托,本是一个非常基础的.NET概念,但前一阵子在园子里却引起轩然大波。先是Michael Tao随笔让人们将委托的写法与茴香豆联系到了一起,接着老赵又用一系列文章分析委托写法的演变,并告诫“嘲笑孔乙己的朋友们,你们在一味鄙视“茴”的四种写法的同时,说不定也失去了一个了解中国传统文化的机会呢!”。

    在我个人看来,委托是.NET Framework中一个非常炫的特性,绝不会向有些评论里说的那样,根本没有机会接触。恰恰相反,我们几乎每天都会接触委托,使用委托。

    其实园子里已经有了很多关于委托的文章,比较有代表性的有:

    1. C# 中的委托和事件及其

    2. C#委托,事件理解入门 (译稿)

    3. 委托揭秘

    4. ……

    本系列试图从个人对于委托的理解展开,对委托的内涵和外延均加以讨论。文中有何不妥或不正确的地方,欢迎大家拍砖斧正。

    好了,下面让我从一个示例开始,一步一步引入委托的概念。

    从示例开始

    假设一个系统的用户登录模块有如下所示的代码

    class User
    {
        public string Name { get; set; }
        public string Password { get; set; }
    }
    
    class UserService
    {
        public void Register(User user)
        { 
            if (user.Name == "Kirin")
            {
                Log("注册失败,已经包含名为" + user.Name + "的用户");
            }
            else
            {
                Log("注册成功!");
            }
        }
        privte void Log(string message)
        {
            Console.WriteLine(message);
        }
    }

    UserService类封装用户登录的逻辑,并根据不同的登录情况向控制台打印不同的日志内容。当程序关闭时,所记录的日志自然也随之消失。

    客户端的代码为

    class Program
    {
        static void Main(string[] args)
        {
            User user = new User { Name = "Kirin", Password = "123" };
            UserService service = new UserService();
            service.Register(user);
            Console.ReadLine();
        }
    }

    使用策略模式

    然而这样的设计肯定是无法满足用户的需求的,用户肯定希望能够查看以前的日志记录,而不仅仅是程序打开以后的内容。如果我们仅仅修改Log方法的实现,那么用户需求再次改变时我们该如何处理呢?难道要无休止地修改Log方法吗?

    既然日志记录的方式是变化的根源,我们自然会想到将其进行封装。我们创建一个名为ILog的接口。

    interface ILog
    {
        void Log(string message);
    }

    并创建两个实现了ILog的类,ConsoleLog和TextLog,分别用来向控制台和文本文件输出日志内容。

    class ConsoleLog : ILog
    {
        public void Log(string message)
        {
            Console.WriteLine(message);
        }
    }
    class TextLog : ILog
    {
        public void Log(string message)
        {
            using (StreamWriter sw = File.AppendText("log.txt"))
            {
                sw.WriteLine(message);
                sw.Flush();
                sw.Close();
            }
        }
    }

    在UserService类中添加一个ILog类型的属性LogStrategy。

    class UserService
    {
        public ILog LogStrategy { get; set; }
        public UserService()
        {
            LogStrategy = new ConsoleLog();
        }
        public void Register(User user)
        { 
            if (user.Name == "Kirin")
            {
                LogStrategy.Log("注册失败,已经包含名为" + user.Name + "的用户");
            }
            else
            {
                LogStrategy.Log("注册成功!");
            }
        }
    }

    客户端代码变为如下形式。

    class Program
    {
        static void Main(string[] args)
        {
            User user = new User { Name = "Kirin", Password = "123" };
            UserService service = new UserService { LogStrategy = new TextLog() };
            service.Register(user);
            Console.ReadLine();
        }
    }

    在声明UserService的时候,还可以将将LogStrategy设置为TextLog。这样在UserService进行逻辑处理时,使用的LogStrategy即为TextLog,日志将输出到文本文件中。

    我们在干什么?我们在重构。重构的结果是什么?重构的结果是实现了一个简单的策略模式。

    使用委托

    然而策略模式仍然不能满足客户的需求,这是为什么呢?

    1. 用户也许会希望自定义Log的实现。当然,你可以通过在客户代码处扩展ILog来实现自己的日志记录方式。如

    class TextBoxLog : ILog
    {
        private TextBox textBox;
        public TextBoxLog(TextBox textBox)
        {
            this.textBox = textBox;
    this.textBox.Multiline = true; }
    public void Log(string message) { textBox.AppendText(message); textBox.AppendText(Environment.NewLine); } }

    但这种方案是否过于复杂呢?如果用户希望在ListView或其他控件上显示,是否需要逐个创建新类呢?并且这样的实现是否与客户端的耦合过于紧密呢?比如用户希望在ListView的各个列中显示日志内容、时间、来源等不同内容,那么在ListViewLog中对ListView硬编码是否很难重用呢?

    2. 用户也许会希望同时使用多种日志记录方式。比如,同时向控制台、文本文件、客户端控件和事件查看器中输出日志。你当然可以在UserService中维护一个List,但这时UserService的职责过多,显然违反了SRP。

    下面介绍本文的主角:委托。

    我们首先来创建一个名为Log的委托,它接收一个string类型的参数。

    public delegate void Log(string message);

    然后在UserService类中添加一个Log委托类型的属性LogDelegate。

    class UserService
    {
        public Log LogDelegate { get; set; }


    // … }

    在客户端,我们直接声明两个静态方法,它们都包含一个string类型的参数,并且没有返回值。

    static void LogToConsole(string message)
    {
        Console.WriteLine(message);
    }
    static void LogToTextFile(string message)
    { 
        using (StreamWriter sw = File.AppendText("log.txt"))
        {
            sw.WriteLine(message);
            sw.Flush();
            sw.Close();
        }
    }

    客户端声明UserService的代码变为

    static void Main(string[] args)
    {
        User user = new User { Name = "Kirin", Password = "123" };
        UserService service = new UserService();
        service.LogDelegate = LogToConsole;
        service.LogDelegate += LogToTextFile;
        service.Register(user);
        Console.ReadLine();
    }

    在构造委托时,我们还可以使用匿名方法和Lambda表达式,在老赵的文章中详细阐述了这些写法的演变。

    对于何时使用委托,何时使用接口(即策略模式),MSDN中有明确的描述

    在以下情况下,请使用委托:

        当使用事件设计模式时。

        当封装静态方法可取时。

        当调用方不需要访问实现该方法的对象中的其他属性、方法或接口时。

        需要方便的组合。

        当类可能需要该方法的多个实现时。

    在以下情况下,请使用接口:

        当存在一组可能被调用的相关方法时。

        当类只需要方法的单个实现时。

        当使用接口的类想要将该接口强制转换为其他接口或类类型时。

        当正在实现的方法链接到类的类型或标识时:例如比较方法。

    您可能觉得上面的例子阐述委托和接口有些过于牵强,事实上有些时候的确很难选择使用接口还是委托。Java中没有委托,但所有委托适用的情况同样可以使用包含单一方法的接口来实现的。在某种程度上,可以说委托是接口(仅定义了单一方法)的一种轻量级实现,它更灵活,也更方便。

    到此为止,我们一步一步用委托重构了最初的代码。再接下来的随笔中,我们将开始更深一步的讨论。

  • 相关阅读:
    POJ 3468 A Simple Problem with Integers
    BZOJ 4430 Guessing Camels
    POJ 2309 BST
    POJ 1990 MooFest
    cf 822B Crossword solving
    cf B. Black Square
    cf 828 A. Restaurant Tables
    Codefroces 822C Hacker, pack your bags!
    [HDU 2255] 奔小康赚大钱
    [BZOJ 1735] Muddy Fields
  • 原文地址:https://www.cnblogs.com/waw/p/2157140.html
Copyright © 2011-2022 走看看