GOF23种设计模式系列之结构型设计模式
目录
什么是结构型设计模式
7种结构型设计模式,关注类与类之间的关系,其实就是折腾组合与继承,为程序提供更好的灵活性和扩展性。
创建型设计模式有以下几种:
- Adapter Class/Object(适配器)
- Bridge(桥接)
- Composite(组合)
- Decorator(装饰)
- Facade(门面)
- Flyweight(享元)
- Proxy(代理)
Adapter Class/Object(适配器)
将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。
分两种:
1. 类型适配器
2. 对象适配器(主流)
类型适配器
/// <summary>
/// 数据访问接口
/// </summary>
public interface IHelper
{
void Add<T>();
void Delete<T>();
void Update<T>();
void Query<T>();
}
/// <summary>
/// 不能修改
/// </summary>
public class RedisHelper
{
public void AddRedis<T>()
{
Console.WriteLine("This is {0} Add", this.GetType().Name);
}
public void DeleteRedis<T>()
{
Console.WriteLine("This is {0} Delete", this.GetType().Name);
}
public void UpdateRedis<T>()
{
Console.WriteLine("This is {0} Update", this.GetType().Name);
}
public void QueryRedis<T>()
{
Console.WriteLine("This is {0} Query", this.GetType().Name);
}
}
调用方式
一:
RedisHelperClass helper = new RedisHelperClass();
helper.Add<Program>();
二:
IHelper helper = new RedisHelperClass(); //new RedisHelper();
helper.Add<Program>();
helper.Delete<Program>();
helper.Update<Program>();
helper.Query<Program>();
对象适配器
/// <summary>
/// 通过组合 对象适配器模式
/// 组合优于继承
/// </summary>
public class RedisHelperObject : IHelper
{
//private RedisHelper _RedisHelper = new RedisHelper();
private RedisHelper _RedisHelper = null;
public RedisHelperObject(RedisHelper redisHelper)
{
this._RedisHelper = redisHelper;
}
public RedisHelperObject()
{
this._RedisHelper = new RedisHelper();
}
public void Add<T>()
{
this._RedisHelper.AddRedis<T>();
}
public void Delete<T>()
{
this._RedisHelper.DeleteRedis<T>();
}
public void Update<T>()
{
this._RedisHelper.UpdateRedis<T>();
}
public void Query<T>()
{
this._RedisHelper.QueryRedis<T>();
}
}
调用方式
IHelper helper = new RedisHelperObject(); //new RedisHelper();
helper.Add<Program>();
helper.Delete<Program>();
helper.Update<Program>();
helper.Query<Program>();
关于适配器模式
适配器的核心在于:包一层! 没有什么技术问题是包一层不能解决的,如果有,就再包一层!
例如:DataAdapter
Bridge(桥接)
搭建一个接口互通的平台(桥),以便接口之间实现多维多样性的组合关系。
/// <summary>
/// 抽象父类
/// </summary>
public abstract class BasePhoneBridge
{
public int Price { get; set; }
public ISystem SystemVersion { get; set; }
/ <summary>
/ 操作系统
/ </summary>
/ <returns></returns>
//public abstract string System();
/ <summary>
/ 系统版本
/ </summary>
/ <returns></returns>
//public abstract string Version();
/// <summary>
/// 打电话
/// </summary>
public abstract void Call();
/// <summary>
/// 发短信
/// </summary>
public abstract void Text();
}
public interface ISystem
{
string System();
string Version();
}
public class IOSSystem : ISystem
{
public string System()
{
return "IOS";
}
public string Version()
{
return "9.4";
}
}
public class AndroidSystem : ISystem
{
public string System()
{
return "Android";
}
public string Version()
{
return "6.0";
}
}
public class WinphoneSystem : ISystem
{
public string System()
{
return "Winphone";
}
public string Version()
{
return "10.0";
}
}
public class iPhoneBridge : BasePhoneBridge
{
public override void Call()
{
Console.WriteLine("Use OS {0}.{1}.{2} Call", this.GetType().Name, this.SystemVersion.System(), this.SystemVersion.Version());
}
public override void Text()
{
Console.WriteLine("Use OS {0}.{1}.{2} Text", this.GetType().Name, this.SystemVersion.System(), this.SystemVersion.Version());
}
}
public class GalaxyBridge : BasePhoneBridge
{
public override void Call()
{
Console.WriteLine("Use OS {0}.{1}.{2} Call", this.GetType().Name, this.SystemVersion.System(), this.SystemVersion.Version());
}
public override void Text()
{
Console.WriteLine("Use OS {0}.{1}.{2} Text", this.GetType().Name, this.SystemVersion.System(), this.SystemVersion.Version());
}
}
public class LumiaBridge : BasePhoneBridge
{
public override void Call()
{
Console.WriteLine("Use OS {0}.{1}.{2} Call", this.GetType().Name, this.SystemVersion.System(), this.SystemVersion.Version());
}
public override void Text()
{
Console.WriteLine("Use OS {0}.{1}.{2} Text", this.GetType().Name, this.SystemVersion.System(), this.SystemVersion.Version());
}
}
调用方式
{
BasePhoneBridge phone = new GalaxyBridge();
phone.SystemVersion = android;
phone.Call();
phone.Text();
}
{
BasePhoneBridge phone = new GalaxyBridge();
phone.SystemVersion = ios;
phone.Call();
phone.Text();
}
{
BasePhoneBridge phone = new GalaxyBridge();
phone.SystemVersion = winphone;
phone.Call();
phone.Text();
}
关于桥接模式
桥接模式用来解决多维度的变化。将多维度的东西封装起来。
上面的例子如果不将System和Version封装起来,会导致出现多个手机,匹配多个系统,又匹配多个版本。修改量无限大
将其封装起来后,修改量可以大幅减少。但上端编写时需要了解具体的细节。
Composite(组合)
将对象组合成树形结构来表示“部分-整体”的层次结构。组合模式使得客户能以一致的方式处理个别对象和组合对象。
public abstract class AbstractDomain
{
public string Name { get; set; }
public double Percent { get; set; }
public abstract void Commission(double total);
public abstract void AddChild(Domain domainChild);
}
public class Domain : AbstractDomain
{
private List<Domain> DomainChildList = new List<Domain>();
public override void AddChild(Domain domainChild)
{
this.DomainChildList.Add(domainChild);
}
public override void Commission(double total)
{
double result = total * this.Percent / 100;
Console.WriteLine("this {0} 提成 {1}", this.Name, result);
foreach (var domainChild in DomainChildList)
{
domainChild.Commission(result);
}
}
}
private static Domain BuildTree()
{
Domain domain = new Domain()
{
Name = "能有的提成收入",
Percent = 10
};
#region
Domain domain1 = new Domain()
{
Name = "CEO",
Percent = 30
};
Domain domain2 = new Domain()
{
Name = "各部门共有",
Percent = 70
};
Domain domain21 = new Domain()
{
Name = "实施",
Percent = 20
};
Domain domain22 = new Domain()
{
Name = "测试",
Percent = 10
};
Domain domain23 = new Domain()
{
Name = "销售",
Percent = 30
};
Domain domain24 = new Domain()
{
Name = "开发",
Percent = 40
};
Domain domain241 = new Domain()
{
Name = "经理",
Percent = 20
};
Domain domain242 = new Domain()
{
Name = "主管",
Percent = 15
};
Domain domain243 = new Domain()
{
Name = "开发团队",
Percent = 65
};
Domain domain2431 = new Domain()
{
Name = "项目组1",
Percent = 50
};
Domain domain2432 = new Domain()
{
Name = "项目组2",
Percent = 50
};
Domain domain24321 = new Domain()
{
Name = "项目经理",
Percent = 20
};
Domain domain24322 = new Domain()
{
Name = "开发人员",
Percent = 80
};
Domain domain243221 = new Domain()
{
Name = "高级开发人员",
Percent = 40
};
Domain domain243222 = new Domain()
{
Name = "中级开发人员",
Percent = 30
};
Domain domain243223 = new Domain()
{
Name = "初级开发人员",
Percent = 20
};
Domain domain243224 = new Domain()
{
Name = "实习生",
Percent = 10
};
Domain domain2432241 = new Domain()
{
Name = "实习生1",
Percent = 25
};
Domain domain2432242 = new Domain()
{
Name = "实习生2",
Percent = 25
};
Domain domain2432243 = new Domain()
{
Name = "实习生3",
Percent = 25
};
Domain domain2432244 = new Domain()
{
Name = "实习生4",
Percent = 25
};
domain243224.AddChild(domain2432241);
domain243224.AddChild(domain2432242);
domain243224.AddChild(domain2432243);
domain243224.AddChild(domain2432244);
domain24322.AddChild(domain243221);
domain24322.AddChild(domain243222);
domain24322.AddChild(domain243223);
domain24322.AddChild(domain243224);
domain2432.AddChild(domain24321);
domain2432.AddChild(domain24322);
domain243.AddChild(domain2431);
domain243.AddChild(domain2432);
domain24.AddChild(domain241);
domain24.AddChild(domain242);
domain24.AddChild(domain243);
domain2.AddChild(domain21);
domain2.AddChild(domain22);
domain2.AddChild(domain23);
domain2.AddChild(domain24);
domain.AddChild(domain1);
domain.AddChild(domain2);
#endregion
return domain;
}
调用方式
double total = 1000000;
Domain domain = BuildTree();
domain.Commission(total);
关于组合模式
有时上端要处理太多角色,为角色提供不同的方案,导致管理的太多。将这些重复的操作封装成提供树结构,使上端无需关注结构。
Decorator(装饰)
在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
/// <summary>
/// 学员基类
/// </summary>
public abstract class AbstractStudent
{
public int Id { get; set; }
public string Name { get; set; }
public abstract void Study();
public void Show()
{
Console.WriteLine("This is {0}", this.Name);
}
}
public class BaseDecorator : AbstractStudent
{
private AbstractStudent _Student = null;
public BaseDecorator(AbstractStudent student)
{
this._Student = student;
}
public override void Study()
{
this._Student.Study();
}
}
public class StudentDecoratorPreview : BaseDecorator
{
public StudentDecoratorPreview(AbstractStudent student)
: base(student)
{
}
public override void Study()
{
Console.WriteLine("课前完成预习准备");
base.Study();
Console.WriteLine("课后做好学习笔记");
}
}
public class StudentDecoratorAnswer : BaseDecorator
{
public StudentDecoratorAnswer(AbstractStudent student)
: base(student)
{
}
public override void Study()
{
base.Study();
Console.WriteLine("老师课后在线答疑");
}
}
public class StudentDecoratorHomework : BaseDecorator
{
public StudentDecoratorHomework(AbstractStudent student)
: base(student)
{
}
public override void Study()
{
base.Study();
Console.WriteLine("巩固练习,学以致用");
}
}
public class StudentDecoratorVideo : BaseDecorator
{
public StudentDecoratorVideo(AbstractStudent student)
: base(student)
{
}
public override void Study()
{
base.Study();
Console.WriteLine("获取视频+课件+代码的回看");
}
}
调用方式
AbstractStudent student = new StudentFree()
{
Id = 123,
Name = "张三"
};
student = new BaseDecorator(student);
student = new StudentDecoratorPreview(student);
student = new StudentDecoratorAnswer(student);
student = new StudentDecoratorHomework(student);
student = new StudentDecoratorVideo(student);
student.Study();
//输出
//课前完成预习准备
//周一到周六上午十点到十一点跟着Eleven老师直播学习.Net高级开发
//课后做好学习笔记
//老师课后在线答疑
//巩固练习,学以致用
//获取视频+课件+代码的回看
关于装饰器模式
在不破坏封装的前提下,给对象扩展新功能这就是AOP面向切面编程核心追求。而装饰器模式就是一种实现方式。
当我们解决了功能的动态扩展,那么在程序设计时,有了新的做法
1. 可以聚集于核心业务逻辑,其他的东西交给AOP来做
2. 对于公共逻辑可以集中管理,统一标准,方便项目管理
Facade(门面)
为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
/// <summary>
/// 门面模式 通常是单例的
/// </summary>
public class FacadeCenter
{
public class IdModel
{
public int Userid { get; set; }
public int productId { get; set; }
public int cityId { get; set; }
}
private static FacadeCenter _FacadeCenter = new FacadeCenter();
private FacadeCenter()
{ }
public static FacadeCenter CreateInstance()
{
return _FacadeCenter;
}
public void NewOrder(int userId, int productId, int cityId)
{
IUserSystem iUserSystem = new UserSystem("为什么");
IStorageSystem iStorageSystem = new StorageSystem();
ILogisticsSystem iLogisticsSystem = new LogisticsSystem();
IOrderSystem iOrderSystem = new OrderSystem();
if (false)
{
Console.WriteLine("用户检测失败");
}
else if (false)
{
Console.WriteLine("仓储检测失败");
}
else if (false)
{
Console.WriteLine("物流检测失败");
//不能直接去物流增加一个检测失败的数据记录
//而是通过物流系统的方法去做
}
else if (false)
{
Console.WriteLine("订单检测失败");
}
else
{
//处理业务数据
Console.WriteLine("添加发货数据");
Console.WriteLine("添加物流数据");
}
}
}
调用方式
int userId = 123;
int productId = 12345;
int cityId = 1;
FacadeCenter facadeCenter = FacadeCenter.CreateInstance();// new FacadeCenter();
facadeCenter.NewOrder(userId, productId, cityId);
关于门面(外观)模式
其实就是对内部诸多接口进行一个封装,提供一个简单的接口给外部调用。
比如,我们写上层时经常会碰见许多要验证的逻辑,我们可以将他们封装到一个类里,对外使用单例模式进行调用。这样既方便了上端也方便了后期修改。
门面模式是我们最常用的模式,并非要局限于处理业务逻辑。只要是可以封装的都可以使用。
Flyweight(享元)
为了解决对象的复用问题,提供第三方的管理,能完成对象的复用
public class FlyweightFactory
{
/// <summary>
/// 准备个容器,数据复用
/// </summary>
private static Dictionary<WordType, BaseWord> FlyweightFactoryDictionary = new Dictionary<WordType, BaseWord>();
private readonly static object FlyweightFactoryLock = new object();
public static BaseWord CreateWord(WordType wordType)
{
if (!FlyweightFactoryDictionary.ContainsKey(wordType))
//是为了优化性能,避免对象已经被初始化后,再次请求还需要等待锁
{
lock (FlyweightFactoryLock)//Monitor.Enter,保证方法体只有一个线程可以进入
{
if (!FlyweightFactoryDictionary.ContainsKey(wordType))
{
BaseWord word = null;
switch (wordType)
{
case WordType.E:
word = new E();
break;
case WordType.L:
word = new L();
break;
case WordType.V:
word = new V();
break;
case WordType.N:
word = new N();
break;
default:
throw new Exception("wrong wordtype!");
}
FlyweightFactoryDictionary.Add(wordType, word);
}
}
}
return FlyweightFactoryDictionary[wordType];
}
}
public enum WordType
{
E,
L,
V,
N
}
调用方式
BaseWord e1 = FlyweightFactory.CreateWord(WordType.E);
BaseWord l = FlyweightFactory.CreateWord(WordType.L);
BaseWord v = FlyweightFactory.CreateWord(WordType.V);
BaseWord n = FlyweightFactory.CreateWord(WordType.N);
Console.WriteLine("{0}{1}{2}{3}{4}{5}",
e1.Get(), l.Get(), e1.Get(), v.Get(), e1.Get(), n.Get());
关于享元模式
看上去享元模式和单例模式很相似,但差距很大:
1. 单例模式因为需要私有构造函数,公开的静态方法,私有的静态变量。所以需要改造类,增加单例逻辑,破坏单例封装 。
2. 享元模式可以自行实例化对象,单例是强制保证的 。
3. 享元工厂也可以初始化多个对象,避免重复的创建和销毁对象。
一般使用于非托管资源(池化资源管理)。需要使用对象可以找享元工厂拿(修改个状态)---用完之后再放回来(状态改回来)
Proxy(代理)
由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
/// <summary>
/// 业务接口
/// </summary>
public interface ISubject
{
/// <summary>
/// get
/// </summary>
/// <returns></returns>
List<string> GetSomethingLong();
/// <summary>
/// do
/// </summary>
void DoSomethingLong();
}
public class ProxySubject : ISubject
{
private static ISubject _iSubject = new RealSubject();
public void DoSomethingLong()
{
try
{
Console.WriteLine("prepare DoSomethingLong");
_iSubject.DoSomethingLong();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
//throw;
}
}
public List<string> GetSomethingLong()
{
try
{
Console.WriteLine("prepare GetSomethingLong");
result = _iSubject.GetSomethingLong();
return result;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw;
}
}
}
/// <summary>
/// 一个耗时耗资源的对象方法
/// 一个第三方封装的类和方法
/// </summary>
public class RealSubject : ISubject
{
public RealSubject()
{
Thread.Sleep(2000);
long lResult = 0;
for (int i = 0; i < 100000000; i++)
{
lResult += i;
}
Console.WriteLine("RealSubject被构造。。。");
}
/// <summary>
/// 一个耗时的复杂查询
/// </summary>
public List<string> GetSomethingLong()
{
Console.WriteLine("读取大文件信息。。。");
Thread.Sleep(1000);
Console.WriteLine("数据库大数据查询。。。");
Thread.Sleep(1000);
Console.WriteLine("调用远程接口。。。");
Thread.Sleep(1000);
Console.WriteLine("最终合成得到结果。。一堆的商品列表");
return new List<string>() { "123", "456", "789" };
}
/// <summary>
/// 一个耗时的复杂操作
/// </summary>
public void DoSomethingLong()
{
Console.WriteLine("下订单。。。");
Thread.Sleep(1000);
Console.WriteLine("短信通知。。。");
Thread.Sleep(1000);
}
}
调用方式
//ISubject subject = new RealSubject();
ISubject subject = new ProxySubject();
subject.GetSomethingLong();
关于代理模式
上面的例子中,上端不去直接调用RealSubject类,而是通过ProxySubject类去调用RealSubject类。这样如果我们想添加其他功能时(例如:日志,异常代理,单例代理,缓存代理,延迟代理,权限代理等),不需要修改RealSubject中的代码,保证RealSubject类的“纯洁性”,而且扩展功对修改关闭。
代理模式的主要优点有:
1. 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
2. 代理对象可以扩展目标对象的功能;
3. 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;
其主要缺点是:
1. 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;增加了系统的复杂度;