为了账号安全,请及时绑定邮箱和手机立即绑定

翻译11 个关键的设计模式

解锁软件架构的秘密,使用《精通软件架构:11个关键设计模式解析》。

目录
  1. 设计模式 — 抽象工厂
  • 学习目标

  • 开始入门

  • 如何使用抽象工厂提供程序?

  • 输出

2. 设计模式 — 适配器

  • 用例

  • 学习目标

  • 开始指南

3. 设计模式 — Builder

  • 用例

  • 学习目标

  • 开始入门

  • 如何从 Main() 方法中使用建造者模式

  • 输出

4. 如何使用责任链模式

  • 用例

  • 开始入门

  • 如何使用责任链模式?

  • 输出

5. 设计模式 — 装饰器

  • 用例

  • 学习目标

  • 开始入门

  • 装饰者模式的应用

  • 完整代码

  • 输出

6. 设计模式 — 工厂方法

  • 学习目标

  • 开始入门

  • 如何使用工厂方法?

  • 输出

7. 设计模式 — 迭代器

  • 用例

  • 开始入门

  • 迭代器模式的应用

  • 输出

8. 设计模式 — 中介者

  • 用例

  • 学习目标

  • 开始入门

  • 如何从主方法中使用中介者模式

9. 设计模式 — 观察者模式

  • 用例

  • 学习目标

  • 开始指南

  • 如何使用观察者模式?

  • 输出

10. C# 8.0 中的 Advance Property 模式

  • 让我们开始吧

  • 带有新 switch 语法的模式匹配程序

  • 测试程序

  • 控制台输出

11. 设计模式 — 单例模式

  • 学习目标

  • 开始入门

  • 输出

  • 线程安全

设计模式 — 抽象工厂

根据四人组的理论,抽象工厂模式可以被视为创建工厂的工厂。

学习目标
  • 什么是抽象工厂设计模式?

  • 如何使用抽象工厂设计模式编写代码?

  • 如何创建一个工厂提供者?

  • 如何从 Main 方法创建一个使用工厂提供程序的客户端应用程序
前置条件

抽象工厂模式完全是工厂方法的扩展;建议在理解抽象工厂设计之前先学习工厂方法。

  • 掌握OOPS概念的基本知识。

  • 任何编程语言知识。
开始指南

让我们考虑任何一家银行的账户类型,例如储蓄账户和支票账户。现在让我们使用抽象工厂设计模式来实现上述示例。

首先,实现ISavingAccount和ICurrentAccount接口,如下所示:

        public interface ISavingAccount {  }
        public interface ICurrentAccount {  }

进入全屏模式 退出全屏模式

在下面的类中继承接口

        public class CurrentAccount : ICurrentAccount
        {
           public CurrentAccount(string message)
           {
            Console.WriteLine(message);
           }
        }
        public class SavingsAccount : ISavingAccount
        {
           public SavingsAccount(string message)
           {
            Console.WriteLine(message);
           }
        }

进入全屏模式 退出全屏模式

让我们为每种账户类型编写一个带有抽象方法的抽象类。

        public abstract class AccountTypeFactory
        {
          public abstract ISavingAccount SavingAccountFactory(string message);
          public abstract ICurrentAccount CurrentAccountFactory(string message);
        }

进入全屏模式 退出全屏模式

现在,让我们创建一个名为“Bank1Factory”的工厂实现,它提供抽象方法的实现。

        public class Bank1Factory : AccountTypeFactory
        {
            public override ICurrentAccount CurrentAccountFactory(string message)
            {
                return new CurrentAccount(message);
            }

            public override ISavingAccount SavingAccountFactory(string message)
            {
                return new SavingsAccount(message);
            }
        }

进入全屏模式 退出全屏模式

抽象工厂设计模式与工厂方法模式的不同之处在于,它需要实现一个工厂提供者,该提供者根据定义返回相应的工厂。

现在我们已经创建了所有的抽象和工厂。让我们来设计工厂提供者。请参见下面的工厂提供者代码片段,其中静态方法将根据账户名称创建一个工厂。

        public class AccountFactoryProvider
        {
            public static AccountTypeFactory GetAccountTypeFactory(string accountName)
            {
                if (accountName.Contains("B1")) { return new Bank1Factory(); }
                else return null;
            }
        }

进入全屏模式 退出全屏模式

如何使用抽象工厂提供程序?

让我们以一个账户号码列表为例,如果账户名称中包含“B1”(完全匹配),那么它将使用通过工厂提供者返回的Bank1Factory实例。

        static void Main(string[] args)
        {
            List<string> accNames = new List<string> { "B1-456", "B1-987", "B2-222" };
            for (int i = 0; i < accNames.Count; i++)
            {
                AccountTypeFactory anAbstractFactory = AccountFactoryProvider.GetAccountTypeFactory(accNames[i]);
                if (anAbstractFactory == null)
                {
                    Console.WriteLine("无效的 " + (accNames[i]));
                }
                else
                {
                    ISavingAccount savingAccount = anAbstractFactory.SavingAccountFactory("Hello saving");
                    ICurrentAccount currentAccount = anAbstractFactory.CurrentAccountFactory("Hello Current");
                }
            }
            Console.ReadLine();
        }

进入全屏模式 退出全屏模式

如果账号名称不包含“B1”字面量,则程序将输出无效的账号名称。

输出

请在下方查看上述代码片段的输出。

        你好,保存 B1-456
        你好,当前 B1-456
        你好,保存 B1-987
        你好,当前 B1-987

进入全屏模式 退出全屏模式

设计模式 — 适配器

根据四人组的定义,适配器模式将一个类的接口转换为客户端期望的接口。

换句话说,适配器设计模式有助于不兼容的接口协同工作。

使用案例

让我们考虑一个例子:两个组织合并,X组织接管了Y组织,但在合并代码时,接口不兼容。假设提供Y组织交易列表的接口与X组织的接口不兼容。

所以适配器设计模式有助于解决这个问题,其实现非常简单。

学习目标
  • 如何使用适配器设计模式进行编码?
开始指南

让我们创建一个组织Y的交易列表,将其转换为组织X客户端应用程序所需的模式。上述类被称为“适配者”。

        public class OrgYTransactions
        {
            public List<string> GetTransactionsList()
            {
                List<string> transactions = new List<string>();
                transactions.Add("借记 1");
                transactions.Add("借记 2");
                transactions.Add("借记 3");
                return transactions;
            }
        }

进入全屏模式 退出全屏模式

其次,让我们创建一个目标接口。

        public interface ITransactions{
          List<string> 获取交易记录();
        }

进入全屏模式 退出全屏模式

现在最后,让我们按照以下方式实现适配器类。

        public class TransAdapter : OrgYTransactions, ITransactions
        {
            public List<string> GetTransactions()
            {
                return GetTransactionsList();
            }
        }

进入全屏模式 退出全屏模式

在完成上述所有实现后,让我们来理解如何在控制台应用程序中使用适配器类。

        class Program
        {
            static void Main(string[] args)
            {
                ITransactions adapter = new TransAdapter();
                foreach (var item in adapter.GetTransactions())
                {
                    Console.WriteLine(item);
                }
            }
        }

进入全屏模式 退出全屏模式

如果你仔细观察下面的用法,我们会使用目标接口ITransactions和适配器类TransAdapter,而无需考虑第三方类OrgYTransactions的接口是如何设计的。这就是适配器设计模式的强大之处,它可以将一个类的接口转换为客户端所需的接口。

设计模式 — Builder

根据“四人组”,“Builder”创建型模式允许将构建某物的特定方法分离并重用。

使用案例

让我们以汽车为例,用户想要构建两种车型,即SUV和轿车。

Builder 设计模式在这种用例中非常有用,让我们一步一步地来看一个演示。

Car 类具有以下属性。

        public class Car{
         public string 名称 { get; set; }
         public double 最高时速 { get; set; }
         public bool 是否为SUV { get; set; }
        }

进入全屏模式 退出全屏模式

学习目标
  • 如何使用建造者设计模式进行编码?
快速入门

首先,让我们实现一个由不同车型(如SUV或轿车)根据使用情况扩展的抽象类构建器。

        public abstract class CarBuilder
        {
            protected readonly Car _car = new Car();
            public abstract void SetName();
            public abstract void SetSpeed();
            public abstract void SetIsSUV();
            public virtual Car GetCar() => _car;
        }

进入全屏模式 退出全屏模式

抽象类包含以下方法

  • 每个 Car 类属性的抽象方法。

  • 一个虚拟方法,用于输出 Car 类的实例。

现在,让我们创建一个工厂,利用 CarBuilder 类来构建不同的汽车型号,并返回所制造汽车的实例。

        public class CarFactory
        {
            public Car Build(CarBuilder builder)
            {
                builder.SetName();
                builder.SetSpeed();
                builder.SetIsSUV();
                return builder.GetCar();
            }
        }

进入全屏模式 退出全屏模式

最后,实现不同的汽车型号。

ModelSuv.cs

        public class ModelSuv : CarBuilder
        {
            public override void SetIsSUV()
            {
                _carSUV = true;
            }

            public override void SetName()
            {
                _car.Name = "马鲁蒂SUV";
            }

            public override void SetSpeed()
            {
                _car.TopSpeed = 1000;
            }
        }

进入全屏模式 退出全屏模式

ModelSedan.cs

        public class ModelSedan : CarBuilder
        {
            public override void SetIsSUV()
            {
                _carSUV = false;
            }

            public override void SetName()
            {
                _car.Name = "马鲁蒂轿车";
            }

            public override void SetSpeed()
            {
                _car.TopSpeed = 2000;
            }
        }

进入全屏模式 退出全屏模式

如何从 Main() 方法中使用建造者模式

最后,我们将使用设计模式并通过 factory.Build() 方法的帮助构建不同的汽车模型。

        static void Main(string[] args)
        {
            var sedan = new ModelSedan();
            var suv = new ModelSuv();
            var factory = new CarFactory();
            var builders = new List<CarBuilder> { suv, sedan };
            foreach (var b in builders)
            {
                var c = factory.Build(b);
                Console.WriteLine($"汽车详情" +
                    $"\n--------------------------------------" +
                    $"\n名称: {c.Name}" +
                    $"\n是否为SUV: {c.IsSUV}" +
                    $"\n最高时速: {c.TopSpeed} 英里/小时\n");
            }
        }

进入全屏模式 退出全屏模式

上述用法展示了我们如何优雅地使用建造者设计模式来构建不同的汽车型号。

代码模式高度可维护且可扩展。如果将来我们需要开发一个新的车型,只需让新车型继承CarBuilder类即可完成。

输出

图片描述

如何使用责任链模式

根据“四人组”,它定义了一条处理请求的责任链。换句话说,将请求从一个对象传递到另一个对象,直到某个对象接受其责任。

使用案例

让我们考虑任何一家公司中的一个索赔系统为例。以下是可批准的价格范围及批准人列表。

        100–1000 卢比 => Junior/Senior 工程师 => 经经理批准
        1001–10000 卢比 => 经理 => 经高级经理批准

进入全屏模式 退出全屏模式

如果金额超出10000的范围,则需要高级经理的特别批准。

上述用例可以轻松地通过责任链设计模式来实现。因此,索赔类具有以下属性。

        public class Claim{
          public int Id { get; set; }
          public double amount { get; set; }
        }

进入全屏模式 退出全屏模式

快速入门

首先,让我们定义一个索赔审批人可以执行的职能,并为不同级别的员工设定一个层级结构。实现如下的抽象类:

        public abstract class ClaimApprover
        {
            protected ClaimApprover claimApprover;
            public void SetHierarchy(ClaimApprover claimApprover)
            {
                this.claimApprover = claimApprover;
            }
            public abstract void ApproveRequest(Claim claim);
        }

进入全屏模式 退出全屏模式

根据用例,让我们来处理“初级/高级”员工的索赔请求。请注意,这类员工的职位无法批准任何索赔。

        public class Junior : ClaimApprover
        {
            public override void ApproveRequest(Claim claim)
            {
                System.Console.WriteLine("无法批准");
            }
        }

进入全屏模式 退出全屏模式

同样地,让我们为经理和高级经理的角色定义实现。

        public class 经理 : 报销审批人
        {
            public override void 审批请求(报销 claim)
            {
                if (claim.金额 >= 100 && claim.金额 <= 1000)
                {
                    System.Console.WriteLine($"报销单号 {claim.Id} 金额 {claim.金额} 已经由经理审批通过");
                }
                else if (claim审批人 != null)
                {
                    claim审批人.审批请求(claim);
                }
            }
        }

进入全屏模式 退出全屏模式

注意,根据金额范围,如果在经理的审批范围内,则经理可以直接批准该申请;否则,请求将被提交给高级经理。

        public class 高级经理 : 报销审批人
        {
            public override void 审批请求(报销 claim)
            {
                if (claim.金额 > 1000 && claim.金额 <= 10000)
                {
                    System.Console.WriteLine($"报销单号 {claim.Id} 金额 {claim.金额} 已由高级经理审批通过");
                }
                else
                {
                    System.Console.WriteLine($"特别审批,报销单号 {claim.Id} 金额 {claim.金额} 已由高级经理审批通过");
                }
            }
        }

进入全屏模式 退出全屏模式

同样地,如果金额范围在高级经理的范围内,该申请可以由经理批准;否则,由于是层级中的最后一位,对于超出范围的金额,将进行特别批准。

        ClaimApprover junior = new Manager();
        ClaimApprover sukhpinder = new Manager();
        ClaimApprover singh = new SeniorManager();
        junior.SetHierarchy(sukhpinder);
        sukhpinder.SetHierarchy(singh);

        Claim c1 = new Claim() { amount = 999, Id = 1001 };
        Claim c2 = new Claim() { amount = 10001, Id = 1002 };
        junior.ApproveRequest(c1);
        sukhpinder.ApproveRequest(c2);

进入全屏模式 退出全屏模式

如何使用责任链模式?

  1. 定义索赔审批人:初级审批人,尽管它无法批准任何索赔。

  2. 定义索赔审批人:经理“sukhpinder”。

  3. 定义索赔审批人:高级经理“Singh”。

  4. 设置一个层级关系,即对于初级员工,索赔审批人是其经理。

  5. 设置经理的层级关系,即索赔审批人是高级经理。

  6. 创建两个不同的索赔范围。

  7. 初级员工将索赔请求发送给经理。

  8. 管理员将索赔请求发送给高级管理员。

输出

        投诉单号 1001,金额 999 的申请已由经理批准。
        投诉单号 1002,金额 10001 的申请已由高级经理特别批准。

进入全屏模式 退出全屏模式

对于第1行的输出,金额在范围内,所以经理批准了它。

对于第2行的输出,虽然高级经理批准了,但金额超出了范围。

设计模式 — 装饰器

根据四人组的定义,该模式可以动态地为类对象添加额外的责任。

使用案例

让我们考虑一下购买价值十万卢比的汽车的例子;公司提供了以下额外功能

  • 天窗

  • 高级音乐系统

  • 以及其他许多功能

添加一些额外功能后,汽车的总价会增加。让我们使用装饰者模式来实现上述用例。

学习目标
  • 如何使用装饰者设计模式进行编码?
开始入门

让我们实现上述定义的用例。首先定义一个抽象类Car及其基础方法。

        public abstract class Car{
          public abstract int CarPrice();
          public abstract string GetName();
        }

进入全屏模式 退出全屏模式

考虑一辆小型汽车,它继承了抽象类Car。

        public class SmallCar : Car{
          public override int CarPrice() => 10000;
          public override string GetName() => "Alto Lxi";
        }

进入全屏模式 退出全屏模式

现在使用 Car 组件实现 CarDecorator 类。

        public class CarDecorator : Car
        {
            protected Car _car;
            public CarDecorator(Car car)
            {
                _car = car;
            }
            public override int CarPrice() => _car.CarPrice();
            public override string GetName() => _car.GetName();
        }

进入全屏模式 退出全屏模式

现在,让我们为 Car 可用的每个额外功能创建一个单独的类,继承 CarDecorator 类。

根据用例,额外的功能包括天窗和高级音响系统。

AdvanceMusic.cs

重写这些方法如下:

  • 将“高级音响系统”的额外成本添加到汽车总价中。

  • 使用附加功能名称更新汽车名称。
        public class AdvanceMusic : CarDecorator
        {
            public AdvanceMusic(Car car) : base(car)
            {
            }

            public override int CarPrice() => _car.CarPrice() + 3000;
            public override string GetName() => "Alto Lxi 配备高级音乐系统";
        }

进入全屏模式 退出全屏模式

Sunroof.cs

重写这些方法如下:

  • 将“天窗”的额外费用添加到汽车总价中。

  • 使用附加功能名称更新汽车名称。
        public class 天窗 : Car装饰器
        {
            public 天窗(Car car) : base(car)
            {
            }
            public override int 车价() => _car.车价() + 2000;
            public override string 获取名称() => "配备天窗的 Alto Lxi";
        }

进入全屏模式 退出全屏模式

装饰器模式的应用

创建 SmallCar 的实例并输出汽车的名称和价格。

        Car 车辆 = new SmallCar();

        Console.WriteLine($"车辆 {车辆.GetName()} 的价格为: " + 车辆.CarPrice());

进入全屏模式 退出全屏模式

现在,让我们像下面这样添加更多功能

        var car1 = new 天窗(car);
        var car2 = new 高级音乐系统(car);

进入全屏模式 退出全屏模式

完整代码

        static void Main(string[] args)
        {
            Car car = new SmallCar();
            Console.WriteLine($"汽车 {car.GetName()} 的价格 : " + car.CarPrice());
            var car1 = new Sunroof(car);
            Console.WriteLine($"汽车 {car1.GetName()} 的价格 : " + car1.CarPrice());
            var car2 = new AdvanceMusic(car);
            Console.WriteLine($"汽车 {car2.GetName()} 的价格 : " + car2.CarPrice());
        }

进入全屏模式 退出全屏模式

输出

图片描述

恭喜..!! 您已成功使用装饰者模式实现用例。

设计模式 — 工厂方法

根据四人组的定义,工厂方法允许子类决定应该创建哪个类的对象。

学习目标
  • 工厂方法设计模式是什么?

  • 如何使用工厂方法编写代码?
开始指南

让我们考虑一个银行的例子,该银行的账户类型包括储蓄账户和支票账户。现在让我们使用工厂设计模式来实现上述例子。

首先,创建一个账户类型的抽象类。

        public abstract class AccoutType
        {
           public string Balance { get; set; }
        }

进入全屏模式 退出全屏模式

实现当前账户和储蓄账户类,继承下面所示的 AccountType 抽象类。

        public class SavingsAccount : AccoutType
        {
         public SavingsAccount()
         {
          Balance = "10000 元";
         }
        }
        public class CurrentAccount : AccoutType
        {
         public CurrentAccount()
         {
          Balance = "20000 元";
         }
        }

进入全屏模式 退出全屏模式

最后,让我们实现工厂接口,该接口将提供一个帮助创建类对象的合同。此接口也被称为创建者。

        public interface IAccountFactory
        {
          AccoutType GetAccoutType(string accountName);
        }

进入全屏模式 退出全屏模式

最后,编写实现创建者接口方法的实现,如下所示。实现创建者的类被称为具体创建者。

        public class AccountFactory : IAccountFactory
        {
            public AccoutType GetAccoutType(string accountName)
            {
                if (accountName.Equals("SAVINGS", StringComparison.OrdinalIgnoreCase))
                {
                    return new SavingsAccount();
                }
                else if (accountName.Equals("CURRENT", StringComparison.OrdinalIgnoreCase))
                {
                    return new CurrentAccount();
                }
                else
                {
                    throw new ArgumentException("无效的账户名称");
                }
            }
        }

进入全屏模式 退出全屏模式

就这样。你已经成功地使用银行示例实现了工厂方法。

如何使用工厂方法?

一个子类将根据账号名称决定创建哪个“AccountType”类的对象。

        class Program
        {
            static void Main(string[] args)
            {
                IAccountFactory accountFactory = new AccountFactory();
                var savingAccount = accountFactory.GetAccoutType("SAVINGS");
                Console.WriteLine("储蓄账户余额: " + savingAccount.Balance);
                var currentAccount = accountFactory.GetAccoutType("CURRENT");
                Console.WriteLine("当前账户余额: " + currentAccount.Balance);
            }
        }

进入全屏模式 退出全屏模式

例如,如果账号名称是“SAVINGS”,那么将创建并返回“SavingAccount”类的对象。

同样地,如果账户名称是“CURRENT”,那么将实例化并返回“CurrentAccount”类的对象。

输出

        存款账户余额:10000 卢比
        活期账户余额:20000 卢比

进入全屏模式 退出全屏模式

设计模式 — 迭代器

根据四人组的定义,迭代器模式提供了一种在不了解聚合对象实现细节的情况下获取聚合对象的方法。

使用案例

让我们以汽车的集合列表和摩托车的 string[] 数组为例,我们需要设计一个聚合对象,以便可以在不知道是列表还是数组的情况下遍历该集合。

迭代器设计模式有助于解决这样一个问题,即标准迭代器可以遍历不同类型的集合。

开始指南

考虑到上述用例,让我们定义一个自定义的迭代器接口,该接口作为列表和数组迭代器之上的抽象层。

        public interface IVehicleIterator{
          void First();
          bool IsDone();
          string Next();
          string Current();
        }

进入全屏模式 退出全屏模式

现在根据用例编写实现上述接口的汽车和摩托车迭代器。

CarIterator.cs

        public class CarIterator : IVehicleIterator
        {
            private List<string> _cars;
            private int _current;
            public CarIterator(List<string> cars)
            {
                _cars = cars;
                _current = 0;
            }
            public string Current()
            {
                return _cars.ElementAt(_current);
            }
            public void First()
            {
                _current = 0;
            }
            public bool IsDone()
            {
                return _current >= _cars.Count;
            }
            public string Next()
            {
                return _cars.ElementAt(_current++);
            }
        }

进入全屏模式 退出全屏模式

汽车迭代器是基于列表集合实现的,并提供了接口方法的实现。

MotorcycleIterator.cs

摩托车迭代器是基于字符串数组(string[])集合实现的,并提供了接口方法的实现。

        public class MotercycleIterator : IVehicleIterator
        {
            private string[] _motercylces;
            private int _current;
            public MotercycleIterator(string[] motercylces)
            {
                _motercylces = motercylces;
                _current = 0;
            }
            public string Current()
            {
                return _motercylces[_current];
            }

            public void First()
            {
                _current = 0;
            }
            public bool IsDone()
            {
                return _current >= _motercylces.Length;
            }
            public string Next()
            {
                return _motercylces[_current++];
            }
        }

进入全屏模式 退出全屏模式

在定义了上述所有迭代器之后,定义一个创建迭代器的标准聚合物对象接口。

        public interface IVehicleAggregate{
           IVehicleIterator 创建迭代器();
        }

进入全屏模式 退出全屏模式

最后,写下实现上述聚合器接口的类。根据用例,Car类和Motorcycle类都将实现聚合器接口。

Car.cs

聚合器接口的方法返回相关的迭代器,如下所示。

        public class Car : IVehicleAggregate
        {
            private List<string> _cars;
            public Car()
            {
                _cars = new List<string> { "Car 1", "Car 2", "Car 3" };
            }

            public IVehicleIterator CreateIterator()
            {
                return new CarIterator(_cars);
            }
        }

进入全屏模式 退出全屏模式

Motorcycle.cs

聚合器接口的方法返回相关的迭代器,如下所示。

        public class Motercycle : IVehicleAggregate
        {
          private string[] _motercycles;
            public Motercycle()
            {
                _motercycles = new[] { "摩托车1", "摩托车2", "摩托车3" };
            }
            public IVehicleIterator CreateIterator()
            {
                return new MotercycleIterator(_motercycles);
            }
        }

进入全屏模式 退出全屏模式

迭代器模式的实际应用

PrintVehicles 方法检查如果 !iterator.isDone,则输出集合元素。无论我们处理的是哪种集合,都实现如 First、IsDone 和 Next 这样的方法。

        static void Main(string[] args)
        {
            IVehicleAggregate car = new Vehicles.Car();
            IVehicleAggregate motercycle = new Vehicles.Motercycle();
            IVehicleIterator carIterator = car.CreateIterator();
            IVehicleIterator motercycleIterator = motercycle.CreateIterator();
            PrintVehicles(carIterator);
            PrintVehicles(motercycleIterator);
        }
        static void PrintVehicles(IVehicleIterator iterator)
        {
            iterator.First();
            while (!iterator.IsDone())
            {
                Console.WriteLine(iterator.Next());
            }
        }

进入全屏模式 退出全屏模式

输出

我们不知道底层的集合类型,但仍然可以通过迭代器设计模式对其进行迭代。如果你继续运行代码,会显示以下输出。

图片描述

设计模式 — 中介者模式

根据四人组的定义,中介者模式封装了对象之间的相互交互。

中介者设计模式通过封装对象间的交互来帮助我们设计松耦合的应用程序。

使用案例

让我们考虑一个聊天室的例子,参与者在其中注册并如何高效地进行交流。

需要使用中介者设计模式实现以下聊天室对话。

        大卫对斯科特: '嘿'
        斯科特对大卫: '我很好,你呢。'
        詹妮弗对阿什利: '嘿阿什利... 大卫又回到了群里'
        詹妮弗对大卫: '你去哪儿了?'
        阿什利对大卫: '你为什么不再这里活跃了?'

进入全屏模式 退出全屏模式

学习目标
  • 如何使用中介者设计模式进行编码?
开始指南

主要步骤是创建一个将在聊天室中使用的用户名列表。下面展示了相应的公共枚举。

        public enum 用户名 {
        Ashley,
        David,
        Jennifer,
        Scott
        }

进入全屏模式 退出全屏模式

现在首先实现聊天室的抽象层。

        public abstract class AChatroom
        {
            public abstract void Register(User user);
            public abstract void Post(string fromUser, string toUser, string msg);
        }

进入全屏模式 退出全屏模式

并且定义了一个包含抽象方法的类。这些方法用于验证用户是否存在于字典中。例如,注册方法会验证用户是否已经存在。如果用户不存在,则将其注册到聊天室中。

        public class Chatroom : AChatroom
        {
            private Dictionary<string, User> _users = new Dictionary<string, User>();
            public override void Post(string fromUser, string toUser, string msg)
            {
                User participant = _users[toUser];
                if (participant != null)
                {
                    participant.DM(fromUser, msg);
                }
            }
            public override void Register(User user)
            {
                if (!_users.ContainsValue(user))
                {
                    _users[user.Name] = user;
                }
                user.Chatroom = this;
            }
        }

进入全屏模式 退出全屏模式

最后,让我们实现用户可以执行的操作,比如在聊天室中向某用户发送消息或接收其他用户发送的私信。

        public class 用户
        {
            private 聊天室 _聊天室;
            private string _名字;
            public 用户(string 名字) => this._名字 = 名字;
            public string 名字 => _名字;
            public 聊天室 聊天室
            {
                set { _聊天室 = value; }
                get => _聊天室;
            }
            public void 发帖(string 到, string 消息) => 
                _聊天室.Post(_名字, 到, 消息);
            public virtual void 私信(string 来自, string 消息) => 
                Console.WriteLine("{0} to {1}: '{2}'", 来自, 名字, 消息);
        }

进入全屏模式 退出全屏模式

如何从主方法中使用中介者模式

        static void Main(string[] args)
        {
            Chatroom 聊天室 = new Chatroom();
            User Jennifer = new UserPersona(Username.Jennifer.ToString());
            User Ashley = new UserPersona(Username.Ashley.ToString());
            User David = new UserPersona(Username.David.ToString());
            User Scott = new UserPersona(Username.Scott.ToString());

            聊天室.Register(Jennifer);
            聊天室.Register(Ashley);
            聊天室.Register(David);
            聊天室.Register(Scott);

            David.Post(Username.Scott.ToString(), "嘿");
            Scott.Post(Username.David.ToString(), "我很好,你呢。");
            Jennifer.Post(Username.Ashley.ToString(), "嘿 Ashley... David 回来了。");
            Jennifer.Post(Username.David.ToString(), "你去哪儿了?");
            Ashley.Post(Username.David.ToString(), "你为什么不再活跃了?");
            Console.ReadKey();
        }

进入全屏模式 退出全屏模式

  1. 创建了 Chatroom 类的对象。

  2. 创建了四个具有唯一名称的用户。

  3. 将他们每个人注册到聊天室。

  4. 用户现在可以开始互相发送消息了。

程序执行仅描述了用户类的Post方法。

输出: 上述程序执行的聊天室历史记录

        大卫对斯科特: '嘿'
        斯科特对大卫: '我很好,你呢。'
        詹妮弗对阿什利: '嘿阿什利... 大卫又回到了群里'
        詹妮弗对大卫: '你去哪儿了?'
        阿什利对大卫: '你为什么不再这里活跃了?'

进入全屏模式 退出全屏模式

设计模式 — 观察者模式

根据四人组的定义,观察者模式定义了两个或多个对象之间的依赖关系。因此,当一个对象的状态发生变化时,所有依赖于它的对象都会收到通知。

换句话说,一个对象的变化会触发另一个对象的通知。

使用案例

让我们以一位拥有“x”名粉丝的Instagram网红为例。每当这位网红发布新内容时,所有粉丝都会收到通知。

让我们使用观察者设计模式实现上述用例。

学习目标
  • 如何使用观察者设计模式进行编码?
开始指南

根据用例,首先实现一个包含名人可以执行的操作的接口,这被称为“主体”。

        public interface ICelebrityInstagram{
         string FullName { get; }
         string Post { get; set; }
         void Notify(string post);
         void AddFollower(IFollower fan);
         void RemoveFollower(IFollower fan);
        }

进入全屏模式 退出全屏模式

Subject 包含以下成员函数。

  • 通知: 通知所有粉丝。

  • AddFollower: 向名人列表中添加一个新的关注者。

  • RemoveFollower: 从名人列表中移除一个粉丝。

现在实现观察者“IFollower”接口,该接口包含用于通知的“Update”成员函数。

        public interface IFollower{
         void 更新(ICelebrityInstagram 名人Instagram);
        }

进入全屏模式 退出全屏模式

最后,是时候为“主题”和“观察者”实现“具体实现”了。

名为“Follower.cs”的 ConcreteObserver

它提供了 Update 成员函数的实现,该函数将名人姓名及帖子输出到控制台。

        public class Follower : IFollower
        {
            public void Update(ICelebrityInstagram celebrityInstagram)
            {
                Console.WriteLine($"粉丝被通知。{celebrityInstagram.FullName}的帖子: " +
                    $"{celebrityInstagram.Post}");
            }
        }

进入全屏模式 退出全屏模式

名为 “Sukhpinder.cs”的 ConcreteSubject

        public class Sukhpinder : ICelebrityInstagram
        {
            private readonly List<IFollower> _posts = new List<IFollower>();
            private string _post;
            public string FullName => "Sukhpinder Singh";

            public string Post {
                get { return _post; }
                set
                {
                    Notify(value);
                }
            }
            public void AddFollower(IFollower follower)
            {
                _posts.Add(follower);
            }
            public void Notify(string post)
            {
                _post = post;
                foreach (var item in _posts)
                {
                    item.Update(this);
                }
            }
            public void RemoveFollower(IFollower follower)
            {
                _posts.Remove(follower);
            }
        }

进入全屏模式 退出全屏模式

如何使用观察者模式?

以下用例显示,每当执行以下语句 sukhpinder.Post = “I love design patterns.”; 时,更新方法将被每个关注者触发,即每个关注者对象都会收到“Sukhpinder”发布的新帖子的通知。

        static void Main(string[] args)
        {
            var sukhpinder = new Sukhpinder();

            var firstFan = new Follower();
            var secondFan = new Follower();
            sukhpinder.AddFollower(firstFan);
            sukhpinder.AddFollower(secondFan);
            sukhpinder.Post = "我爱设计模式。";
            Console.Read();
        }

进入全屏模式 退出全屏模式

输出

图片描述

C# 8.0 中的 Advance Property 模式

文章描述了模式匹配如何提供了一种有效的方式来利用和处理那些不是主要系统组成部分的数据形式。

让我们开始

让我们以 toll calculator 为例,看看模式匹配如何帮助编写该算法。

文章中使用的实体类

        public class Car
          {
              public int 乘客数量 { get; set; }
          }
          public class DeliveryTruck
          {
              public int 重量 { get; set; }
          }
          public class Taxi
          {
              public int 车费 { get; set; }
          }
          public class Bus
          {
              public int 容量 { get; set; }
              public int 乘客数量 { get; set; }
          }

进入全屏模式 退出全屏模式

示例1:根据以下条件计算过路费:

  • 如果车辆是 Car => 100 卢比

  • 如果车辆是 DeliveryTruck => 200 卢比

  • 如果车辆是公交车 => 150 卢比

  • 如果车辆是出租车 => 120 卢比

带有新 switch 语法的模式匹配程序

如果车辆类型匹配到Car,则返回100,依此类推。注意,null和{}是对象类型的默认情况。

此外,可以使用“_”来编程默认场景。**参见新的 switch 语法。**

这是一种更干净且高效的编码方式,同时也建议在 switch 语法中使用单字母变量名。

        public static int TollFare(Object vehicleType) => vehicleType switch
        {
         Car c => 100,
         DeliveryTruck d => 200,
         Bus b => 150,
         Taxi t => 120,
         null => 0,
         { } => 0
        };

进入全屏模式 退出全屏模式

测试以上程序

从控制台应用程序的角度测试示例。以下代码示例说明了如何从主方法中调用上述模式匹配函数。

        var car = new Car();
        var taxi = new Taxi();
        var bus = new Bus();
        var truck = new DeliveryTruck();

        Console.WriteLine($"一辆小汽车的过路费是 {TollFare(car)}");
        Console.WriteLine($"一辆出租车的过路费是 {TollFare(taxi)}");
        Console.WriteLine($"一辆公交车的过路费是 {TollFare(bus)}");
        Console.WriteLine($"一辆卡车的过路费是 {TollFare(truck)}");

进入全屏模式 退出全屏模式

控制台输出

        小汽车的过路费是100
        出租车的过路费是120
        公交车的过路费是150
        卡车的过路费是200

进入全屏模式 退出全屏模式

示例 2:根据车辆类型添加占用定价

  • 没有乘客的汽车和出租车需额外支付10卢比。

  • 携带两名乘客的汽车和出租车可享受10卢比的折扣。

  • 携带三名或以上乘客的汽车和出租车可享受20卢比的折扣。

  • 乘客少于50%的公共汽车需额外支付30卢比。

  • 拥有超过90%乘客的公共汽车可享受40卢比的折扣。

  • 重量超过5000磅的卡车额外收取100卢比。

  • 轻型货车(低于3000磅),给予20卢比的折扣。

模式匹配开关

参考具有单个及多个属性类的模式匹配语法。**链接**

模式匹配 — 车辆实体

        Car { PassengerCount: 0 } => 100 + 10,
        Car { PassengerCount: 1 } => 100,
        Car { PassengerCount: 2 } => 100 - 10,
        Car c => 100 - 20,

进入全屏模式 退出全屏模式

模式匹配 — 出租车实体

        出租车 { 乘车费用:0 } => 100 + 10,
        出租车 { 乘车费用: 1 } => 100,
        出租车 { 乘车费用: 2 } => 100 - 10,
        出租车 t => 100 - 20,

进入全屏模式 退出全屏模式

模式匹配 — 公交实体

        当 ((double)b.RidersCount / (double)b.Capacity) < 0.50 时,Bus b => 150 + 30,

        当 ((double)b.RidersCount / (double)b.Capacity) > 0.90 时,Bus b => 150 - 40,

        其他情况,Bus b => 150,

进入全屏模式 退出全屏模式

模式匹配 — 送货车实体

        当 (t.Weight > 5000) 时,DeliveryTruck t => 200 + 100,
        当 (t.Weight < 3000) 时,DeliveryTruck t => 200 - 20,
        DeliveryTruck t => 200,

进入全屏模式 退出全屏模式

结合所有实体

下面的例子展示了模式匹配的优势:模式分支是按顺序编译的。编译器还会警告关于不可达代码的情况。

        public static int OccupancyTypeTollFare(Object vehicleType) => vehicleType switch
          {
              Car { PassengerCount: 0 } => 100 + 10,
              Car { PassengerCount: 1 } => 100,
              Car { PassengerCount: 2 } => 100 - 10,
              Car c => 100 - 20,
              Taxi { Fare: 0 } => 100 + 10,
              Taxi { Fare: 1 } => 100,
              Taxi { Fare: 2 } => 100 - 10,
              Taxi t => 100 - 20,
              Bus b when ((double)b.RidersCount / (double)b.Capacity) < 0.50 => 150 + 30,
              Bus b when ((double)b.RidersCount / (double)b.Capacity) > 0.90 => 150 - 40,
              Bus b => 150,
              DeliveryTruck t when (t.Weight > 5000) => 200 + 100,
              DeliveryTruck t when (t.Weight < 3000) => 200 - 20,
              DeliveryTruck t => 200,
              null => 0,
              { } => 0,
          };

进入全屏模式 退出全屏模式

测试以上程序

从控制台应用程序的角度进行测试示例。以下代码展示了如何从主方法中调用上述模式匹配函数。

        var car1 = new Car{ PassengerCount=2};
        var taxi1 = new Taxi { Fare = 0 };
        var bus1 = new Bus { Capacity = 100, RidersCount = 30 };
        var truck1 = new DeliveryTruck { Weight = 30000 };

        Console.WriteLine($"汽车的过路费是 {OccupancyTypeTollFare(car1)}");
        Console.WriteLine($"出租车的过路费是 {OccupancyTypeTollFare(taxi1)}");
        Console.WriteLine($"公交车的过路费是 {OccupancyTypeTollFare(bus1)}");
        Console.WriteLine($"货车的过路费是 {OccupancyTypeTollFare(truck1)}");

进入全屏模式 退出全屏模式

控制台输出

        小汽车的过路费是90
        出租车的过路费是110
        公交车的过路费是180
        卡车的过路费是300

进入全屏模式 退出全屏模式

“模式匹配使代码更易读,并在无法向类中添加代码时提供了面向对象技术的替代方案。”

设计模式 — 单例模式

Gang of Four — 单例设计模式确保某个类只有一个实例/对象,并且提供一个全局访问点。

学习目标
  • 如何使用单例设计模式编写代码?
开始指南

单例类用于消除一个特定类的多个对象的实例化。

        public class SingletonExample
        {
            private string Name { get; set; } = "来自单例的问候";
            private static SingletonExample _instance;
            public static SingletonExample Instance
            {
                get
                {
                    if (_instance == null)
                    {
                        _instance = new SingletonExample();
                    }
                    return _instance;
                }
            }
            public SingletonExample()
            {
            }
            public string GetName() => Name;
        }

进入全屏模式 退出全屏模式

分解

  1. 迭代 1 _instance==null 表示只会创建一次实例。

  2. 迭代2,现在 _intance != null,所以将返回之前创建的实例。
使用控制台应用程序进行测试

让我们两次调用单例类,并将返回的实例赋值给两个不同的变量。最后,使用Object.Equals函数检查这两个对象是否相等。

        static void Main(string[] args)
        {
            var response = SingletonExample.Instance;
            Console.WriteLine(response);

            var response1 = SingletonExample.Instance;
            Console.WriteLine(response1);
            Console.WriteLine(Object.Equals(response1, response));
        }

进入全屏模式 退出全屏模式

  • 如果返回 true,意味着每次都会生成单例实例。

  • 如果返回 false,表示该类没有遵循单例模式。

输出

控制台输出返回 true;恭喜你,你已经成功实现了 Singleton 模式。

图片描述

线程安全

上述类被称为单例类,但目前它不是线程安全的。在多线程环境中,两个线程可能会同时击中 if (_instance == null) 语句,最终会导致单例类生成多个实例。

一种更安全的线程使用方式是采用锁机制,另一种方式是创建一个只读实例,以实现更干净和高效的方案。

        public class 线程安全单例
        {
            private static readonly 线程安全单例 _instance = new 线程安全单例();
            public static 线程安全单例 实例
            {
                get
                {
                    return _instance;
                }
            }
            public 线程安全单例()
            {
            }
        }

进入全屏模式 退出全屏模式

Github 示例

https://github.com/ssukhpinder/设计模式

感谢您的阅读!

提示帮助我继续维护和构建这样的新项目,并在评论中分享您的想法。

C# 编程🚀

感谢您成为C#社区的一员!

跟随我在 Medium 上的内容

请考虑通过点击"买我一杯咖啡"来支持我

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消