很多程序员用面向对象语言写了多年的代码, 仍然不知道设计模式为何物, 这不奇怪, 设计模式并不是非有不可,可是它能让代码变的更美好。
程序员大可闷头堆代码, 复制粘贴, 然后不断的感慨代码难以维护, 难以复用, 难以扩展, 而继续不思进取。
当然也可以选折不断去追求更美好, 更合理的代码, 把自己从bug调试, 需求变动等噩梦中拯救出来, 进而真正体会到编码的乐趣。
你如果选折后者那么设计模式对于你而言, 是真正必不可少的, 只有当你真正认真考虑过代码的复用性,扩展性,和合理性时, 你才能真正体会到设计模式的好处。
想知道一样东西的价值, 最好的方法就是想想, 如果没有它, 你会怎么样.
对于学习模式, 也是一样, 不要拘泥于其中, 要多想想为什么需要它. 如果没有这种模式, 一样是很好的code, 那就说明这个模式在这种情况下没有价值, 那就不要去用他.
如果使用了一种模式, 能使你的code从可扩展性, 可维护性和可复用性上有些提高, 那么这个就是他的价值所在.
下面是面向对象设计的6大原则,是学习编程和模式的基础
单一职责原则 (SRP)
一个类,只有一个引起它变化的原因。应该只有一个职责。如果一个类有一个以上的职责,这些职责就耦合在了一起。这会导致脆弱的设计。当一个职责发生变化时,可能会影响其它的职责. 这严重影响了代码的可维护性, 可复用性.
开闭原则(OCP)
开闭原则是面向对象设计中“可复用设计”的基石,是面向对象设计中最重要的原则之一,其它很多的设计原则都是实现开闭原则的一种手段。1988 年,Bertrand Meyer在他的著作《Object Oriented Software Construction》中提出了开闭原则,它的原文是这样:“Software entities should be open for extension,but closed for modification”。翻译过来就是:“软件实体应当对扩展开放,对修改关闭”。一句好说不好做的话......
依赖倒置原则(DIP)
依赖倒置原则(Dependence Inversion Principle)就是要依赖于抽象,不要依赖于具体。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。
A.高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。
B.抽象不应该依赖于具体,具体应该依赖于抽象。
依赖倒置原则的本质就是通过抽象(接口或者抽象类)使各个类或模型的实现彼此独立,不互相影响,实现模块间的松耦合。
接口隔离原则(ISP)
接口是我们设计时对外提供的契约,通过分散定义多个接口,可以预防未来变更的扩散,提高系统的灵活性和可维护性。
要求在接口中尽量少公布public方法,而不是建立一个庞大的臃肿的接口,容纳所有的客户端访问。
接口是对外的承诺,承诺地越少对系统开发越有利,变更的风险也就越少,同时也有利于降低成本。
迪米特法则(LoD,LKP)
迪米特法则(Law of Demeter,LoD)也称为最少知识原则(Least Knowledge Principle,LKP), 迪米特法则的核心观念就是类间解耦,弱耦合. 一个类应该对自己需要耦合或调用的类知道得最少,你(被耦合或调用的类)的内部是如何复杂都和我没关系,那是你的事情,我就知道你提供的public方法,我就调用这么多,其他的一概不关心。
设计模式的门面模式(Facade)和中介模式(Mediator),都是迪米特法则应用的例子。
里氏替换原则 (LSP)
Liskov 于1987年提出了一个关于继承的原则“Inheritance should ensure that any property proved about supertype objects also holds for subtype objects.”——“继承必须确保超类所拥有的性质在子类中仍然成立。”也就是说,当一个子类的实例应该能够替换任何其超类的实例时,它们之间才具有 is-A关系。 该原则称为Liskov Substitution Principle——里氏替换原则。
继承是个耦合性很强的关系, 所以不能随便轻率的使用继承关系, 如果子类并不能完全替换父类的话, 应该使用聚集, 组合来代替继承.
举个例子, 鸟-->企鹅, 可是企鹅不会飞, 无法实现fly函数, 这样的继承关系就违反了里氏替换原则.
工厂模式概述
工厂模式, 包括简单工厂, 工厂方法, 抽象工厂, 为什么叫工厂, 因为工厂生产出来的产品是规格一致的(即接口一致的, 具有统一基类的), 否则叫作坊.
规格一致的好处是代码复用, 基于多态和里氏替换原则, 所有的代码都可以基于基类去编写. 当在不同的子类间切换时, 不需要更改任何代码, 因为根据里氏替换原则, 子类是可以完全替换父类的.
简单工厂模式
简单工厂模式是属于创建型模式,又叫做静态工厂方法(StaticFactory Method)模式,但不属于23种GOF设计模式之一。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。
场景, 在客户代码中, 需要根据不同的情况创建不同的对象.
那么直接的做法, 用大量的if... else...来判断, 条件不同创建不同的对象.
这样好不好? 明显不好, 如果需要选择的对象很多, 那在客户代码中需要加上大段的条件判断, 严重影响可读性, 使代码重点不突出. 而且也严重影响维护和扩展性, 每次对象增加都要来修改这段代码.
怎么办? 最简单的办法就是, 把这段代码挪到其他地方去, 这样客户端代码就清晰了, 可读性就强了, 这个就是简单工厂模式.
该模式其实只是对象创建代码做了简单的封装, 把对象创建的职责(大段的if...else...)放到一个简单工厂中, 而在客户代码只需提供简单参数, 就可以得到相应的对象, 而不需要关心对象创建过程.
这个模式的好处就是做到职责单一原则, 把复杂的对象创建代码放到专门的工厂类中去, 这样客户代码的可读性, 可维护性都大大增强.
缺点就是, 当需创建的对象很多, 逻辑复杂时, 工厂类本身的可读性和可维护性仍然比较差.
public class SimpleFactory{
public static Mobile createMobile(String mobileName){
if(mobileName.equals("NOKIA")){
new Nokia();
}else if(mobileName.equals("MOTOROLA")){
new Motorola();
}else{
throw new Exception("还不支持该种类型的手机生产!");
} } }
客户代码,
object = SimpleFactory.createMobile('NOKIA')
客户这样就可以简单的获取对象, 而不关心具体的对象场景过程, 而且通过简单的修改参数就可以得到不同的对象.
工厂方法模式
简单工厂模式其实是把问题转移了, 并没有真正去解决. 把创建对象的逻辑从客户代码移到工厂类后, 工厂类仍然违反开闭原则, 每次增加新的对象都要修改代码, 而且当需要创建的对象很多时, 条件判断会很长很混乱, 严重影响可读性和维护性. 那怎么样才能真正解决这个问题? 思路是把一个工厂类化为许许多多的工厂类, 既然当一长串条件创建语句都放在一个工厂类中时显的混乱,难维护. 那么就把他打散, 将各个对象的创建过程分别抽象为多个具体的工厂类(如下图). 这样就符合开闭原则了,当增加对象时, 我们只需要加上一个具体的工厂类即可, 不需要修改本来的工厂类的代码.
客户代码就由,
object = SimpleFactory.createMobile('NOKIA')
换成,
Ifactory factory= new NokiaFactory()
object = factory.createMobile()
可见对于客户代码而言, 简单工厂和工厂实际没有区别的. 当用户需要切换对象时, 只需要修改一处代码就可以实现, 其他地方都可以利用多态来避免代码的修改.
工厂方法的好处就是让工厂类具有可扩展性, 符合开闭原则.
坏处是, 当要创建的对象很多时, 需要创建大量的具体工厂类.
抽象工厂模式
抽象工厂模式就是工厂方法模式的扩展. 建立一个工厂时, 不仅仅可以创建一个对象, 而是可以创建一系列的对象.
工厂方法的例子,
Ifactory factory= new NokiaFactory()
object = factory.createMobile()
现在Nokia不光有Mobile, 也生产TV, 那就要提供
factory.createTV()
这就是抽象工厂, 如下图, 和工厂方法唯一的区别就是工厂中可以同时生产多个产品.
抽象工厂模式的缺点很明显, 当你要增加一个产品时, 相当麻烦. 其他不谈, 要在抽象工厂类和所有派生类里面增加create接口就相当的烦人.
简单工厂模式改进
前面介绍了简单工厂-->工厂方法-->抽象工厂模式这样的改进思路, 但是好像这样是越改越复杂, 虽然增加了可扩展性, 但是付出的代价似乎大了点, 有没有捷径?|
通常而言, 捷径是没有的, 但是这里的情况是有时会有, 因为对于某些语言支持....(不知怎么描述这种特性, 语句动态执行?), 如.Net里面的反射, 或python里面的eval
有了这样的特性, 我们就有一种新的去除大量条件语句的思路,
如下面的例子, .Net我不熟, 就以Python为例子,
很清楚可以看到, 在createMobile函数中, 我们需要使用大量的条件语句, 随着品牌的变多, 这个函数会越来越长, 越来越复杂.
而对于createMobileEval, 无论你增加多少品牌, 现在的代码都已经足够, 有了这种语言特性, 非常简单的就解决了问题.
如果你需要增加产品, 只需要在加个函数就可以, 如createTVEval
为了自由切换品牌或品牌系列而不用改变任何客户代码, 我们还可以把切换开关(需要的品牌名)放到全局变量里, 或配置文件里. 这样就perfect了......
有了这种方法根本就不需要使用工厂方法和抽象工厂, 模式是死的, 技术是活的......
策略模式
策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。
场景, 对于输入, 在不同的情况下有不同的处理逻辑
那么直接的做法, 仍然用大量的if... else...来判断, 条件不同使用不同的逻辑函数来处理.
没有评论:
发表评论