【问题标题】:What is the use of Template Method in Base Classes?基类中模板方法的用途是什么?
【发布时间】:2012-07-04 15:48:45
【问题描述】:

嗯,我正在阅读 MSDN 上关于“基类用法”的优秀文章 this。虽然我了解基类和接口的概念,但我无法理解本文第二段(“受保护的方法和构造函数”)中模板方法的用法。

谁能通过一个简单的实际示例帮助我理解这个概念?或许,了解模板方法的概念是一个很好的起点。

提前致谢。

【问题讨论】:

    标签: c# base-class template-method-pattern


    【解决方案1】:

    这个想法是你有多个公共重载一个方法,所有内部都使用一个方法。因此,没有一个公共重载本身具有实现。相反,受保护的方法用于所有重载的实际实现。

    所以首先,您不要重复自己,因为您只有一次实现,所有具有默认值的重载只需通过设置一些默认值来调用实现。

    现在在继承类时,派生类可以简单地重写内部实现一次,所有以前的公共重载都将立即使用新的实现。因此,您可以使用标准实现在基类中指定公共接口,但允许派生类在遵守接口约定的同时更改该实现。

    现在有人可能会争论为什么将实现放在一个单独的方法中,我真的不知道。相反,人们可以轻松地实现一个方法的最通用签名,并简单地让其他方法调用该方法而不是内部方法。使用单独方法的原因可能是您可以添加对公共方法不可见的内部使用参数,但我想这取决于您想要做什么。

    【讨论】:

    • 谢谢!在​​这种特殊情况下,信息是否隐藏了将实现与公共方法分开的驱动因素。 (除了保持结构/工作流程相同。)如果我理解正确,那么我认为它是一个非常好的编程结构。现在我想知道使用这种模式有什么缺点。
    【解决方案2】:

    您可以搜索模板方法设计模式。这个模式包括一个模板方法,它提供了一个框架调用序列的方法。一个或多个步骤可以推迟到实现这些步骤的子类,而不改变整个调用顺序。示例:

    // Template Method pattern -- Structural example
    
    using System;
    
    
    
    namespace DoFactory.GangOfFour.Template.Structural
    
    {
    
      /// <summary>
    
      /// MainApp startup class for Real-World
    
          /// Template Design Pattern.
    
          /// </summary>
    
          class MainApp
    
      {
    
        /// <summary>
    
        /// Entry point into console application.
    
        /// </summary>
    
        static void Main()
    
        {
    
          AbstractClass aA = new ConcreteClassA();
    
          aA.TemplateMethod();
    
    
    
          AbstractClass aB = new ConcreteClassB();
    
          aB.TemplateMethod();
    
    
    
          // Wait for user
    
          Console.ReadKey();
    
        }
    
      }
    
    
    
      /// <summary>
    
      /// The 'AbstractClass' abstract class
    
      /// </summary>
    
      abstract class AbstractClass
    
      {
    
        public abstract void PrimitiveOperation1();
    
        public abstract void PrimitiveOperation2();
    
    
    
        // The "Template method"
    
        public void TemplateMethod()
    
        {
    
          PrimitiveOperation1();
    
          PrimitiveOperation2();
    
          Console.WriteLine("");
    
        }
    
      }
    
    
    
      /// <summary>
    
      /// A 'ConcreteClass' class
    
      /// </summary>
    
      class ConcreteClassA : AbstractClass
    
      {
    
        public override void PrimitiveOperation1()
    
        {
    
          Console.WriteLine("ConcreteClassA.PrimitiveOperation1()");
    
        }
    
        public override void PrimitiveOperation2()
    
        {
    
          Console.WriteLine("ConcreteClassA.PrimitiveOperation2()");
    
        }
    
      }
    
    
    
      /// <summary>
    
      /// A 'ConcreteClass' class
    
      /// </summary>
    
      class ConcreteClassB : AbstractClass
    
      {
    
        public override void PrimitiveOperation1()
    
        {
    
          Console.WriteLine("ConcreteClassB.PrimitiveOperation1()");
    
        }
    
        public override void PrimitiveOperation2()
    
        {
    
          Console.WriteLine("ConcreteClassB.PrimitiveOperation2()");
    
        }
    
      }
    
    }
    

    参考:Template Method Design Pattern

    【讨论】:

      【解决方案3】:

      在我看来那是一篇很老的文章了,不记得用 Impl 命名了。

      我认为维基百科有更好的描述:
      模板方法用于:

      • 让子类实现(通过方法覆盖)可以变化的行为

      • 避免代码重复:一般工作流结构在抽象类的算法中实现一次,在每个子类中实现必要的变化。

      • 控制在什么时候允许子类化。与简单的多态覆盖相反,其中基本方法将被完全重写,从而允许对工作流进行彻底更改,只允许更改工作流的特定细节。

      作为模板模式应用结果的控制结构(控制反转)通常被称为好莱坞原则:“不要打电话给我们,我们会打电话给你。”利用这个原理,父类中的模板方法根据需要调用子类方法来控制整个流程。

      简单来说,你在基类中定义骨架,派生类实现实现之间的差异。

      假设我们有信息,必须发布到不同的渠道。
      所以我们创建了基类 Publisher,它具有如何做到这一点的框架。
      我们强制实现初始化,每个派生都会设置发布地址。
      我们制作了适合大多数通道的发送实现,如果某些通道使用 ftp 而不是 http,我们让覆盖发送。
      并且所有通道的日志记录到 dababase 都是相同的,所以我们不允许覆盖它。
      只有发布对 Publisher 派生类的用户感兴趣,所以只有该方法是公共的。

      public abstract class Publisher 
      {
            private address;
            // if you wish to force implementation in derived class, make method abstract
            private abstract void Initialize();
            // if you wish optional implementation in derived class, make it virtual
            protected virtual void SendChangesToWeb() 
            {
               // ...
               webClient.Upload(address, data)
            }
      
            // if you wish that some step could not be changed from outside 
            private void LogSentChangesToDatabase() 
            {
               // ... save date time when was send and what was sent
            }
      
            // this sequence is the same for all derives, no point to duplicate 
            public void PublishUpdates() 
            {
                 Initialize();
                 SendChangesToWeb();
                 LogSentChangesToDatabase();
            }
      }
      
      public class GooglePublisher : Publisher {
           private override Initialize() 
           {
               address = "http://www.google.com";
           }         
      }
      
      public class FtpPublisher : Publisher {
           private override Initialize() 
           {
               address = "ftp://test.com";
           }     
      
           protected override SendChangesToWeb() 
           {
              FtpClient.Upload(address, data)
           }
      }
      

      【讨论】:

      • 感谢 Giedrius,这是一个非常有用的示例。我想我现在真的对这个概念有了一些了解。但我不知道是否值得深入研究,或者是否有人在实际世界中使用这种风格。
      • 命名风格 - 不,但是使用了模式,有用且非常自然(自然 - 我的意思是我在不知道这是一个众所周知的模式的情况下使用它:)。
      • :-) 也许我没有注意到这一点。虽然它简单而整洁。感谢您的所有帮助。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-04-24
      • 1970-01-01
      • 2011-02-03
      • 2011-09-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多