设计模式,是一个程序员稍微进阶点的问题了。
之前在开发的时候用过不少方法,包括Instance单例模式(最常见的就是游戏中的各种全局管理器等),以及订阅-发布(又称观察者模式)等,
当时用的时候纯粹是觉得用这些方法能够优雅些,能够使代码整洁、规整下。也没想过这些事什么,
直到最近无意看到一本书《大话设计模式》,才意识到编程中的设计问题。
原来编程不只是能写出代码,能写出功能,如何把代码写得更加优雅,更加健硕,能减低各种耦合,能考虑后期维护成本,才是王道。
我认真地读了下这本书,写一些笔记,算是心得体会吧。
(注:学习设计模式首推《深入浅出设计模式》,一本外国人写的很有趣的书,《大话设计模式》是国人模仿那本书写的,总体还算不错,
但我发现有一些问题,书里有些例子举得有些牵强,感觉是为了举例而举例,比如第一章,在设计计算器那块,明明可以用委托实现的简单功能,
作者偏偏要举工厂模式的例子,不知道是我领悟不够还是怎么的,反正我觉得举错例子还不如不举例子)
先来个UML类图说明吧,介绍模式的时候偶尔会用到:
一、简单工厂模式
简单工厂模式是属于创建型模式,又叫做静态工厂方法(Static Factory Method)模式,但不属于23种GOF设计模式之一。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式,可以理解为是不同工厂模式的一个特殊实现。(我懒,直接复制百度百科)
就好比,你在游戏里需要为主角创建衣服,裤子,鞋子,帽子等物品,但你总不能为每一种物品的创建都单独写一种实现吧,
player.shoe = new Shoe(); player.trousers = new Trousers(); player.clothes = new Clother();
这种写法明显有问题,具体是什么问题,我TM全忘了了。。==,只是觉得这样写很不好看。
正确的做法是:
1.将所有要创建的对象抽象出一个共同的父类,例如这里:
class Equip{},里面描述所有子类的相同信息
2.所有要生成的物品继承Equip,在工厂类中为每种物品实现生产的具体函数,例如:CreateShoe()
工厂类的大概实现如下:
class Factor{ public static Equip Create(EquipType type){
switch(type){
case Shop:
return CreateShoe();
break;
case Clother:
...
} }
Equip createShoe(){}
... }
3.创建物品时,直接通过工厂向工厂类中传入参数来指定生成哪一种产品:
player.shoe = (Shoe)Factory.Create(EquipType.shop);
player.clothes = (Clothes )Factory.Create(EquipType.clothes );
最后,我们分析下该模式的优缺点:
优点
工厂类是整个模式的关键.包含了必要的逻辑判断,根据外界给定的信息,决定究竟应该创建哪个具体类的对象.通过使用工厂类,外界可以从直接创建具体产品对象的尴尬局面摆脱出来,仅仅需要负责“消费”对象就可以了。而不必管这些对象究竟如何创建及如何组织的.明确了各自的职责和权利,有利于整个软件体系结构的优化。
缺点
由于工厂类集中了所有实例的创建逻辑,违反了高内聚责任分配原则,将全部创建逻辑集中到了一个工厂类中;它所能创建的类只能是事先考虑到的,如果需要添加新的类,则就需要改变工厂类了。
当系统中的具体产品类不断增多时候,可能会出现要求工厂类根据不同条件创建不同实例的需求.这种对条件的判断和对具体产品类型的判断交错在一起,很难避免模块功能的蔓延,对系统的维护和扩展非常不利;
这些缺点在工厂方法模式中得到了一定的克服。
二、单例模式
// 懒汉式 public class SingletonClass{ private static SingletonClass instance=null; public static SingletonClass getInstance() { if(instance==null) { instance=new SingletonClass(); } return instance; } private SingletonClass(){ } }
但以上有些问题,就是在多线程下易出错误,比如两个线程同时调用该单例,而且该单例还未实例化,
那么就会出现以下问题,线程1访问时,由于instance == null,所有线程一会实例化该单例,而线程2访问时,
instance也有可能为null,所以他也实例化一次,这就会出现问题,解封方法就是用双向锁:
// 双从锁定 class Singleton{ private static Singleton instance=null; private static readonly object syncRoot = new object(); private Singleton(){ //do something } public static Singleton getInstance(){ if(instance==null){ lock(syncRoot){ if(null==instance){ instance=new Singleton(); } } } return instance; } }
如果是C#语言的话,还可以用静态初始化的方法,那样更简洁:
// C#静态初始化方法(即饿汉式方法) public sealed class Singleton { private static readonly Singleton instance = new Singleton(); private Singleton() {} public static Singleton GetInstanceJ() { return instance; } }
扩展: Unity3D实现继承于MonoBehaviour的单例
由于MonoBehaviour的特殊性(不能new,自动消耗,自动调用周期函数),所以实现起来稍微有点不一样:
要点:
a.其他部分跟普通单例差不多,新增的Awake里的代码,是由于U3D有AddCompent的特性,避免单例组件被多次Add导致重复生成单例对象。
b.DontDestoryOnLoad是由于U3D的所有对象都在切换场景时释放,为了避免单例对象被释放,应该加这一句。
还可以将上面的实现抽成抽象,做成一套模板,这样就可以省事很多,毕竟一个游戏里可能有会很多单例管理器:
至于泛型的理解,参考我这篇文章中的第八条。
注:该模版只是将单例的通用创建以及处理方式抽象出来而已,被写死在模板里,如果要利用此模板实现特定的单例,
必须得用继承,例如:class MyUIManager : Singleton<UIManager>,然后使用的时候就MyUIManager.Instacne即可。
三、依赖导致原则