面向对象23种设计模式系列(二)- 结构型设计模式
结构型设计模式:关注类与类之间的关系。结构型设计模式的核心:组合优于继承(横向关系优于纵向关系)。
纵向关系:继承≈≈实现 超强关联
横向关系:>组合>聚合>关联>依赖
依赖是出现在方法内部(在类A的方法内部对类B进行声明和初始化)
另外三个是用语义区分的,可能都是一个属性
Person类 有个大脑Header属性 组合(同生共死)
Person类 有个手/脚属性 聚合(成人)
Person类 有个iPhone属性 关联(非必须)
多种结构型设计模式其实都是用组合包一层,然后加功能,解决不同的问题,然后有不同的侧重点,也有不同的规范。
结构型设计模式(7个):适配器模式、代理模式、装饰器模式、外观模式、组合模式、桥接模式、享元模式。
下面我们结合几种具体场景来分析几种结构性设计模式。
1、适配器模式(Adapter Pattern)
解决重构的问题,新东西和旧系统不吻合,通过继承/组合进行适配。
场景:程序中已经确定好数据访问规范IDBHelper,我们的MySqlHelper、SqlServerHelper和OracleHelper都遵循了这个规范。随着业务的变化,某一天我们的数据库可能需要迁移到MongoDB,而它的数据访问使用的是第三方的MongoDBHelper,其数据访问规范和我们现有的IDBHelper规范不一致(没有实现IDBHelper接口),但是呢我们又无法修改第三方的代码。这时候我们就需要做相应的适配了。
程序原有代码:
using System; namespace AdapterPattern { /// <summary> /// 数据访问接口 /// </summary> public interface IDBHelper { void Add<T>(); void Delete<T>(); void Update<T>(); void Query<T>(); } }
using System; namespace AdapterPattern { /// <summary> /// MySql数据访问 /// </summary> public class MySqlHelper : IDBHelper { public void Add<T>() { Console.WriteLine("This is {0} Add", this.GetType().Name); } public void Delete<T>() { Console.WriteLine("This is {0} Delete", this.GetType().Name); } public void Update<T>() { Console.WriteLine("This is {0} Update", this.GetType().Name); } public void Query<T>() { Console.WriteLine("This is {0} Query", this.GetType().Name); } } }
using System; namespace AdapterPattern { /// <summary> /// SqlServer数据访问 /// </summary> public class SqlServerHelper : IDBHelper { public void Add<T>() { Console.WriteLine("This is {0} Add", this.GetType().Name); } public void Delete<T>() { Console.WriteLine("This is {0} Delete", this.GetType().Name); } public void Update<T>() { Console.WriteLine("This is {0} Update", this.GetType().Name); } public void Query<T>() { Console.WriteLine("This is {0} Query", this.GetType().Name); } } }
using System; namespace AdapterPattern { /// <summary> /// Oracle数据访问 /// </summary> public class OracleHelper : IDBHelper { public void Add<T>() { Console.WriteLine("This is {0} Add", this.GetType().Name); } public void Delete<T>() { Console.WriteLine("This is {0} Delete", this.GetType().Name); } public void Update<T>() { Console.WriteLine("This is {0} Update", this.GetType().Name); } public void Query<T>() { Console.WriteLine("This is {0} Query", this.GetType().Name); } } }
第三方的MongoDBHelper
using System; namespace AdapterPattern { /// <summary> /// MongoDB数据访问 /// </summary> public class MongoDBHelper { public MongoDBHelper() { Console.WriteLine($"构造MongoDBHelper"); } public void AddMongoDB<T>() { Console.WriteLine("This is {0} Add", this.GetType().Name); } public void DeleteMongoDB<T>() { Console.WriteLine("This is {0} Delete", this.GetType().Name); } public void UpdateMongoDB<T>() { Console.WriteLine("This is {0} Update", this.GetType().Name); } public void QueryMongoDB<T>() { Console.WriteLine("This is {0} Query", this.GetType().Name); } } }
可以看出程序原有的规范和第三方的规范不一致,那我们如何去做适配呢?
方法1:类适配器模式
继承:既满足现有的规范调用,又没有修改MongoDBHelper。
using System; namespace AdapterPattern { /// <summary> /// 类适配器模式 /// 继承 既满足现有的规范调用,又没有修改MongoDBHelper /// </summary> public class MongoDBHelperInherit : MongoDBHelper, IDBHelper { public MongoDBHelperInherit() { Console.WriteLine($"构造{this.GetType().Name}"); } public void Add<T>() { base.AddMongoDB<T>(); } public void Delete<T>() { base.DeleteMongoDB<T>(); } public void Update<T>() { base.UpdateMongoDB<T>(); } public void Query<T>() { base.QueryMongoDB<T>(); } } }
方法2:对象适配器模式
组合:既满足现有的规范调用,又没有修改MongoDBHelper。
using System; namespace AdapterPattern { /// <summary> /// 对象适配器模式 /// 组合 既满足现有的规范调用,又没有修改MongoDBHelper /// </summary> public class MongoDBHelperCompose : IDBHelper { private MongoDBHelper _mongoDBHelper; private readonly MongoDBHelper _mongoDBHelper1 = new MongoDBHelper(); //1、属性注入 声明写死 一定有 /// <summary> /// 2、构造函数注入 /// 可以替换(需要抽象) _mongoDBHelper一定有值(不考虑其他构造函数) /// </summary> public MongoDBHelperCompose(MongoDBHelper mongoDBHelper) { _mongoDBHelper = mongoDBHelper; Console.WriteLine($"构造{this.GetType().Name}"); } /// <summary> /// 3、方法注入 可以替换(需要抽象) _mongoDBHelper不一定有值 /// </summary> public void SetObject(MongoDBHelper mongoDBHelper) { _mongoDBHelper = mongoDBHelper; } public void Add<T>() { _mongoDBHelper.AddMongoDB<T>(); } public void Delete<T>() { _mongoDBHelper.DeleteMongoDB<T>(); } public void Update<T>() { _mongoDBHelper.UpdateMongoDB<T>(); } public void Query<T>() { _mongoDBHelper.QueryMongoDB<T>(); } } }
那为什么说组合优于继承呢?
二者都会先构造一个MongoDBHelper,但是继承是强侵入,父类的东西子类必须有
灵活性,继承只为一个类服务;组合可以面向抽象为多个类型服务
2、代理模式(Proxy Pattern)
代理模式:
包一层:没有什么技术问题是包一层不能解决的,如果有,就再包一层。
vpn代理 FQ代理 火车票代理。。。
通过代理业务类去完成对真实业务类的调用,代理类不能增加业务功能。
通过代理,能够为对象扩展功能(不是增加业务)而不去修改原始业务类,也就是包了一层,我的地盘听我的。比如来个日志记录,异常处理,可以避免修改业务类,只需要修改代理类。
场景:我们都知道如果要真实的去火车站买票则去火车站的路上需要发费一段时间,到了火车站可能查询火车票也要发费一点时间,查询到有票了这时候又需要排队买票,又要发掉一段时间。为了解决这个问题,这个时候就有可能出现很多火车票代理点,这些代理点就可以代理查询火车票和售票业务。代理点只负责代理相关业务,不进行增加业务,不会出现比如说火车站没有提供选座业务,但是代理点却增加了这个业务。下面我们通过代码来看下。
真实业务:
using System; namespace ProxyPattern { /// <summary> /// 售票业务接口 /// </summary> public interface ISellTickets { bool GetSomething(); void DoSomething(); } }
using System; using System.Threading; namespace ProxyPattern { /// <summary> /// 真实售票业务类 /// 一个耗时耗资源的对象方法 /// 一个第三方封装的类和方法 /// </summary> public class RealSellTickets : ISellTickets { public RealSellTickets() { Thread.Sleep(2000); long lResult = 0; for (int i = 0; i < 100000000; i++) { lResult += i; } Console.WriteLine("RealSellTickets被构造。。。"); } /// <summary> /// 火车站查询火车票 /// </summary> public bool GetSomething() { Console.WriteLine("坐车去火车站看看余票信息。。。"); Thread.Sleep(3000); Console.WriteLine("到火车站,看到是有票的"); return true; } /// <summary> /// 火车站买票 /// </summary> public void DoSomething() { Console.WriteLine("开始排队。。。"); Thread.Sleep(2000); Console.WriteLine("终于买到票了。。。"); } } }
代理类:
using System; using System.Collections.Generic; namespace ProxyPattern { /// <summary> /// 售票业务代理类 /// </summary> public class ProxySellTickets : ISellTickets { private readonly ISellTickets _sellTickets = new RealSellTickets(); //private static ISellTickets _sellTickets = new RealSellTickets(); //构造函数耗时耗资源,可改成单例 public void DoSomething() { try { Console.WriteLine("prepare DoSomething..."); //此处可进行扩展功能,比如来个日志记录,可以避免修改业务类,只需要修改代理类。 _sellTickets.DoSomething(); //代理业务 } catch (Exception ex) { Console.WriteLine(ex.Message); //此处可进行异常处理的扩展 throw ex; } } private static Dictionary<string, bool> _proxyDictionary = new Dictionary<string, bool>(); //缓存处理 public bool GetSomething() { try { Console.WriteLine("prepare GetSomething..."); string key = "Proxy_GetSomething"; bool bResult = false; if (!_proxyDictionary.ContainsKey(key)) { bResult = _sellTickets.GetSomething(); //代理业务 _proxyDictionary.Add(key, bResult); } else { bResult = _proxyDictionary[key]; } return bResult; } catch (Exception ex) { Console.WriteLine(ex.Message); throw ex; } } } }
可以看出ProxySellTickets完成了对RealSellTickets的代理,其实就是包了一层。通过代理业务类去完成对真实业务类的调用,代理类不能增加业务功能,但是可以扩展一些功能,比如日志记录、缓存处理、异常处理等。
3、装饰器模式(Decorator Pattern)
装饰器模式:
结构型设计模式巅峰之作,组合+继承。
通过组合+继承,完成对象功能动态扩展。
场景:很多人应该都上过网课,知道不同的VIP学员可能有不同的学习权限,比如:具有观看视频直播权限的普通VIP学员、具有视频回看权限的VIP学员、具有课程免费升级的VIP学员,另外可能还有点评、巩固练习等权限。现在我们希望为不同的学员去定制不同的权限,这些权限的个数能随意定制,顺序也能随意定制,并且随着业务的变化权限类型可能还在不断的增加,这时候我们又希望在不改变原有代码的情况下还能去动态的扩展新的功能。这种场景就可以考虑使用装饰器模式了。
下面我们来重点看下代码:
using System; namespace DecoratorPattern { /// <summary> /// 抽象学员 /// </summary> public abstract class AbstractStudent { public int Id { get; set; } public string Name { get; set; } public abstract void Study(); } }
using System; namespace DecoratorPattern { /// <summary> /// 一个普通的vip学员,学习vip课程 /// </summary> public class StudentVip : AbstractStudent { public override void Study() { Console.WriteLine("{0} is a vip student studying .net Vip", base.Name); } } }
using System; namespace DecoratorPattern.Decorator { /// <summary> /// 装饰器的基类 /// 继承+组合 /// 也是一个学员,继承了抽象类 /// </summary> public class BaseStudentDecorator : AbstractStudent { private readonly AbstractStudent _student; //用了组合加override public BaseStudentDecorator(AbstractStudent student) { this._student = student; } public override void Study() { this._student.Study(); //Console.WriteLine("******************************"); //基类装饰器必须是个空的行为 会重复 } } }
using System; namespace DecoratorPattern.Decorator { /// <summary> /// 巩固练习 /// </summary> public class StudentHomeworkDecorator : BaseStudentDecorator { public StudentHomeworkDecorator(AbstractStudent student) : base(student) //调用父类的构造函数 { } public override void Study() { base.Study(); Console.WriteLine("巩固练习"); } } }
using System; namespace DecoratorPattern.Decorator { /// <summary> /// 点评 /// 父类是BaseStudentDecorator,爷爷类是AbstractStudent /// </summary> public class StudentCommentDecorator : BaseStudentDecorator { public StudentCommentDecorator(AbstractStudent student) : base(student) //调用父类的构造函数 { } public override void Study() { base.Study(); Console.WriteLine("点评"); } } }
using System; namespace DecoratorPattern.Decorator { /// <summary> /// 视频代码回看 /// </summary> public class StudentVideoDecorator : BaseStudentDecorator { public StudentVideoDecorator(AbstractStudent student) : base(student) //调用父类的构造函数 { } public override void Study() { base.Study(); Console.WriteLine("视频代码回看"); } } }
using System; namespace DecoratorPattern.Decorator { /// <summary> /// 课程免费升级 /// </summary> public class StudentUpdateDecorator : BaseStudentDecorator { public StudentUpdateDecorator(AbstractStudent student) : base(student) //调用父类的构造函数 { } public override void Study() { base.Study(); Console.WriteLine("课程免费升级"); } } }
使用(核心):
using System; using DecoratorPattern.Decorator; namespace DecoratorPattern { /// <summary> /// 装饰器模式:结构型设计模式巅峰之作,组合+继承。 /// 通过组合+继承,完成对象功能动态扩展。 /// </summary> class Program { static void Main(string[] args) { { AbstractStudent student = new StudentVip() { Id = 381, Name = "隔壁老王" }; student = new StudentHomeworkDecorator(student); //巩固练习 student = new StudentCommentDecorator(student); //点评 student = new StudentVideoDecorator(student); //视频代码回看 student = new StudentUpdateDecorator(student); //课程免费升级 student.Study(); //先调用基类Study方法,再处理自己的逻辑 } Console.ReadKey(); } } }
运行结果:
PS:装饰器基类的Study方法中只能有代理业务,不能有别的,不然会重复。
Demo源码:
链接:https://pan.baidu.com/s/1EACr1x3VkiNL-tMaqknDnw 提取码:ud7f
此文由博主精心撰写转载请保留此原文链接:https://www.cnblogs.com/xyh9039/p/13301890.html
版权声明:如有雷同纯属巧合,如有侵权请及时联系本人修改,谢谢!!!