对于类和接口,总共有两种,下面只有一行表示方法的接口以及下面有两行分别显示属性和方法的类。
就一个类而言,应有且仅有一个导致它被修改的原因。
也就是说,我们在设计类时,应当尽量保证每一个类只承担一项工作的职责,只有当这项职责发生变化,我们才需要修改类。如果一个类承担了过多的职责,那么在他的多个职责中无论哪一个发生了变化,我们需要改变这个类的作用,都必须停下他所承担的其他职责的工作,从而丧失效率,甚至由于代码耦合而造成其他职责的破坏。
即
如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设计,当修改发生时,设计会遭受意想不到的破坏。
对于软件实体(类、模块、方法等)应该可以扩展,但是不可修改。
在做任何系统的时候,我们都不可能指望对其的需求会一直不变,那不科学也不现实。所以我们需要保证整个系统面对需求的变更可以相对容易修改,而不是每次需求的变化我们都需要推到现有的重来。
该原则的核心是,面对需求,对程序的改动是通过增加新代码进行的,而不是更改现有代码。
但是无论模块多么“封闭”,都会存在一些无法使之继续封闭的情况。既然不可能完全封闭,设计人员必须对于他设计的模块应该对那种变化封闭做出选择。他必须先猜测出最有可能发生变化的种类,然后构造抽象来隔离这些变化。
也就是说,当系统出现了一个很小的需求变化时,我们就应该去考虑这一类变化的应对措施,对现有类进行抽象来提前做好防范。
即,
当我们在写第一个代码时,假设变化不会发生,一旦发生一个变化,我们就应该创建抽象(利用继承和多态)来应对这一类的变化。
因此,我们会希望在展开工作后不久就能够知道我们以后可能会遭遇到什么样的需求变化,查明可能发生的变化所等待的时间越长,要创建正确的抽象就越困难。
开放-封闭原则是面向对象设计的核心所在,遵循这个原则可以带来面向对象技术所声称的巨大好处,即可维护、可扩展、可复用、灵活性好。让开发人员进队程序中呈现出频繁变化的哪些部分做出抽象。当然,对于应用程序中每个部分都刻意的进行抽象同样不是一个好主意。拒绝不成熟的抽象和抽象本身一样重要。
在软件中,把一个父类替换成其任何一个子类,程序的行为都应当没有变化。
比如说,鸟类我们一般会定义上能飞的方法,而鸵鸟不能飞,那么在这种情况下,我们就不应该让鸵鸟去继承鸟类,因为如果我们需要使用鸟类的飞翔方法时,鸵鸟将会为我们带来错误(bug)。
针对接口编程,而不是针对实现编程。
举个例子,我们不能让高层模块依赖于低层模块。
在面向过程开发时,为了能够复用常用代码,会把常用代码写成代码库。比如访问数据库,增删改查一类。每次做新的项目时,我们就去调用这些代码库中的代码,这就是高层模块依赖于低层模块。
而这种方式一旦低层模块出现问题,或者因为其他原因(比如客户指定了数据库而你的低层模块用的另一个数据库)无法使用时,会导致我们的高层模块,自己写的模块也无法复用。
而我们在面向对象中,则应该让两者都依赖于接口或抽象类,毕竟由于里氏代换原则,我们继承自接口和抽象类的实现类都必须能够替换或者实现接口和抽象类的全部功能。这样无论低层模块发生了什么问题,我们也只需要根据接口去更换低层模块。
策略模式是一种定义一系列算法的设计模式,从概念上来看,所有这些算法完成的都是相同的工作(如在图示中他们都具有acceptCash方法),只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合。
如图,我们有三个正在运行的算法,他们主要的方法为acceptCash()方法,它的返回值为一个double类型的数值。
我们可以将acceptCash方法提出,写在基类CashSuper中,让这三个类作为其子类,让另一个类CashContext作为和客户端程序的接口,在其中引用父类CashSuper,通过判断运行时的情况,将三个子类赋值给父类的变量,动态调用三个作为子类的算法。
在运行中,这以设计模式经常和简单工厂模式相结合,使“判断并赋值”这一过程也在context类中完成,这样就可以让客户端不必在认识基类CashSuper了。
//工厂类CashContext的实现
public class CashContext {
private double result;//三个算法返回的结果都是double型,我们可以直接交给客户端程序
public void setTypes(int choice) {
CashSuper cs;//我们需要有一个基类(或接口)变量来接收判断的子类
switch (choice) {
case 1:
cs = new CashNormal();//Java的多态。
break;
case 2:
cs = new CashRoubate();//将派生类赋值给基类
break;
case 3:
cs = new CashReturn();
break;
default:
break;
}
this.result = cs.acceptCash();//赋值完成后运行运算方法
}
public double getResult() {
return result;
}
策略模式有几个个优点,
装饰模式是为已有的功能动态地添加更多功能的一种形式。而这些新加入的功能,经常是为了满足在某种特殊情况下的特殊需要,仅仅是装饰了现有的类和方法。
因此,我们需要一种模式来动态的在现有的类的方法的基础上增加新的代码。这就是装饰模式。其构造如图所示。
如图中,我们想要扩展ConcreteComponent类,他有一个接口是Component,里面包含了我们想要扩展的该类的方法Operation()。之后我们写一个抽象类Decorator,其中包含有扩展方法Operation(),之后我们们可以创建类来继承Decorator类,在其中包含Operation()方法。
Decorator类的构造如下方代码。除了我们要扩展的operation方法外,我们还需要一个设置Component的方法。
public class Decorator implements Component {
private Component factory;
/**
* Title:
* Description:
* @param factory
*/
public void decorate(Component factory) {
this.factory = factory;
}
public void Operation() {
if (factory!=null) {
this.factory.Operation();
}
}
public Component getFactory() {
return factory;
}
}
在具体实现类decoratorA中,我们只需要在写完要扩展的代码适当的位置,调用继承下来的Decorator类中的Component对象的Operation方法即可。
public class DecoratorA extends Decorator{
@Override
public void Operation() {
System.out.println("do something");
getFactory().Operation();
}
}
而在客户端类中我们则需要这么调用
private void prepare() {
DecoratorA fDecoratorA=new DecoratorA();
DecoratorB fDecoratorB = new DecoratorB();
fDecoratorA.decorate(this);
fDecoratorB.decorate(fDecoratorA);
fDecoratorB.operation();
}
就想我们看到的那样,我们只需要最后的对象(这里是DecoratorB)调用Operation方法即可,因为在DecoratorB对象中,在执行玩operation方法的代码之后,会执行他本身的factory对象的operation方法,而factory对象则是它在上一行decorate方法中赋值的DecorateA。