一、简单工厂模式
1.面向对象三大基本特性:封装、继承、多态
封装:封装是指将现实世界中存在的某个客体的属性与行为绑定在一起,并放置在一个逻辑单元内。该逻辑单元负责将所描述的属性隐藏起来,外界对客体内部属性的所有访问只能通过提供的用户接口实现。这样做既可以实现对客体属性的保护作用,又可以提高软件系统的可维护性。只要用户接口不改变,任何封装体内部的改变都不会对软件系统的其他部分造成影响。
继承:继承性是子类自动共享父类数据结构和方法的机制,这是类之间的一种关系。在定义和实现一个类的时候,可以在一个已经存在的类的基础之上来进行,把这个已经存在的类所定义的内容作为自己的内容,并加入若干新的内容。
多态:多态的基础是继承。多态下,子类重写父类的方法,实际使用过程中,用父类引用变量指向子类对象,此时父类引用调用的是子类重写后的方法,由于是父类引用变量,所有无法调用子类特有的方法。如果想调用子类特有的方法,必须强制类型转换到子类。
class Person { public void speak() { System.out.println("person"); } } class Chinese extends Person { @Override public void speak() { System.out.println("Chinese"); } public void chinaNo1() { System.out.println("chinaNo1"); } } class English extends Person { @Override public void speak() { System.out.println("English"); } } public class SimpleFactory { public static void main(String args[]) { Person person1 = new Chinese(); Person person2 = new English(); person1.speak(); person2.speak(); //person1.chinaNo1(); 多态下父类引用变量无法直接调用子类特有的方法 Chinese chinese = (Chinese) person1; chinese.chinaNo1(); } }
2.简单工厂模式
import java.util.Scanner; class Operation { private double numberOne = 0; private double numberTwo = 0; public double getNumberOne() { return numberOne; } public void setNumberOne(double numberOne) { this.numberOne = numberOne; } public double getNumberTwo() { return numberTwo; } public void setNumberTwo(double numberTwo) { this.numberTwo = numberTwo; } public double getResult() throws Exception { double result = 0; return result; } } class OperationAdd extends Operation { @Override public double getResult() { return getNumberOne() + getNumberTwo(); } } class OperationSub extends Operation { @Override public double getResult() { return getNumberOne() - getNumberTwo(); } } class OperationMul extends Operation { @Override public double getResult() { return getNumberOne() * getNumberTwo(); } } class OperationDiv extends Operation { @Override public double getResult() throws Exception { if (getNumberTwo() == 0) { throw new Exception("除数不能为0"); } return getNumberOne() / getNumberTwo(); } } class OperationFactory { public static Operation createOperation(char operation) throws Exception { switch (operation) { case '+': { return new OperationAdd(); } case '-': { return new OperationSub(); } case '*': { return new OperationMul(); } case '/': { return new OperationDiv(); } default: { throw new Exception("不支持的计算类型"); } } } } public class SimpleFactory { public static void main(String args[]) { Scanner sc = new Scanner(System.in); double number1 = sc.nextDouble(); System.out.println(number1); char op = sc.next().charAt(0); System.out.println(op); double number2 = sc.nextDouble(); System.out.println(number2); Operation operation = null; try { operation = OperationFactory.createOperation(op); } catch (Exception e) { e.printStackTrace(); } operation.setNumberOne(number1); operation.setNumberTwo(number2); try { System.out.println(operation.getResult()); } catch (Exception e) { e.printStackTrace(); } } }
3.UML类图
关联(association):当一个类“知道”另一个类时,可以用关联。企鹅“知道”气候的变化。
聚合(Aggregation):表示一种弱的“拥有”关系,体现的是A对象可以包含B对象,但B对象不是A对象但一部分。一个雁群可以有多只大雁,满足聚合关系。
合成(Composition):是一种强的“拥有”关系,体现了严格的部分和整体的关系,部分和整体的生命周期一样。
依赖(Dependency):动物依赖氧气和水,它们是依赖关系。
二、策略模式
面相对象的编程,并不是类越多越好,类的划分是为了封装,但分类但基础是抽象,具有相同属性和功能的抽象集合才是类。例如打折,打一折和打九折只是形式的不同,抽象分析出来,所有的打折方法都是一样的,所以打折算法应该是一个类。
策略模式(Strategy):它定义类算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。
1.简单工厂模式与策略模式的结合
class CashContext { private CashSuper cashSuper; public CashContext(String type) { if (type.equals("无优惠")) { cashSuper = new CashNormal(); } else if (type.equals("打八折")) { cashSuper = new CashRebate(0.8); } else if (type.equals("满300减20")) { cashSuper = new CashReturn(300, 20); } } public double getResult(double money) { return cashSuper.getResult(money); } } class CashSuper { public double getResult(double money) { double result = 0; return result; } } class CashRebate extends CashSuper { private double rebate; public CashRebate(double rebate) { this.rebate = rebate; } @Override public double getResult(double money) { return money * rebate; } } class CashNormal extends CashSuper { @Override public double getResult(double money) { return money; } } class CashReturn extends CashSuper { private double full; private double reduction; public CashReturn(double full, double reduction) { this.full = full; this.reduction = reduction; } @Override public double getResult(double money) { return money - (money >= full ? Math.floor(money / full) * reduction : 0); } } public class Strategy { public static void main(String args[]) { CashContext cashContext = new CashContext("无优惠"); System.out.println(cashContext.getResult(1000)); cashContext = new CashContext("打八折"); System.out.println(cashContext.getResult(1000)); cashContext = new CashContext("满300减20"); System.out.println(cashContext.getResult(1000)); } }
上面的例子是简单工厂模式和策略模式的结合,从上一章可以看出,简单工厂模式需要让用户知道两个类(Operation和OperationFactory),而策略模式只需要认识一个类(CashContext)就可以类,耦合度降低。这里我们在客户端实例化的是CashContext的对象,调用的是CashContext的方法getResult,这是的收费算法彻底的与客户端分离。连算法的父类CashSuper都不让客户端认识了。
2.策略模式解析
策略模式是一种定义一系列算法的方法,从概念上来看,所有这些算法完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合[DPE]。
策略模式的Strategy类(对应例子中的CashSuper)层次为Context定义类一系列的可供重用的算法或行为。继承有助于析取出这些算法中的公共功能[DP]。
策略模式的优点是简化了单元测试,因为每个算法都有自己的类,可以通过自己的接口单独测试[DPE]。
当不同的行为堆砌在一个类中时,就很难避免使用条件语句来选择合适的行为。将这些行为封装在一个个独立的Strategy类中,可以在使用这些行为的类中消除条件语句[DP]。
策略模式就是用来封装算法的,但在实践中,我们发现可以用它来封装几乎任何类型的规则,只要在分析过程中听到需要在不同时间应用不同的业务规则,就可以考虑使用策略模式处理这种变化的可能性[DPE]。
在基本的策略模式中,选择所用具体实现的职责由客户端对象承担,并转给策略模式的Context对象[DPE]。这本身没有解除客户端需要选择判断的压力,而策略模式与简单工厂模式结合后,选择具体实现的职责也可以由Context来承担,这就最大化地减轻了客户端的职责。
但它依然不够完美,因为在CashContext里还是用到来switch,也就是说,要增加一种算法,就必须更改switch,更好的办法是使用反射技术(注:在抽象工厂模式章节有对反射的讲解)。
三、单一职责原则
单一职责原则:就一个类而言,应该仅有一个引起它变化的原因[ASD]。
如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设计,当变化发生时,设计会遭受到意想不到的破坏[ASD]。
四、开放——封闭原则
开放——封闭原则:软件实体(类、模块、函数等等)应该可以扩展,但是不可修改[ASD]。
这个原则其实有两个特征,一个是说“对于扩展是开放的(Open for extension)”,另一个是说“对于更改是封闭的(Closed for modification)”[ASD]。
在我们最初编写代码时,假设变化不会发生。当变化发生时,我们就创建抽象来隔离以后发生的同类变化[ASD]。
面对需求,对程序的改动是通过增加新代码进行的,而不是更改现有的代码[ASD]。
开放——封闭原则是面相对象设计的核心所在。遵循这个原则可以带来面相对象技术所声称的巨大好处,也就是可维护、可扩展、可复用、灵活性好。开发人员应该仅对程序中呈现出频繁变化的那些部分作出抽象,然而,对于应用程序中的每个部分都进行抽象同样不是一个好主意。拒绝不成熟的抽象和抽象本身一样重要[ASD]。
五、依赖倒转原则
依赖倒转原则:
1.高层模块不应该依赖低层模块。两个都应该依赖抽象。
2.抽象不应该依赖细节。细节应该依赖抽象。[ASD]
不管高层模块还是低层模块,它们都依赖于抽象,具体一点就是接口或抽象类,只要接口是稳定的,那么任何一个的更改都不用担心其他收到影响,这就使得无论高层模块还是低层模块都可以很容易地被复用。
1.里氏代换原则
里氏代换原则(LSP):子类型必须能够替换掉它们的父类型。[ASD]
一个软件实体如果使用的是一个父类的话,那么一定适用于其子类,而且它察觉不出父类对象和子类对象的区别。也就是说,在软件里面,把父类都替换成它的子类,程序的行为没有变化,简单的说,子类型必须能够替换掉它们的父类型[ASD]。
只有当子类可以替换掉父类,软件单位的功能不受到影响时,父类才能真正被复用,而子类也能够在父类的基础上增加新的行为。
由于子类型的可替换性才使得使用父类类型的模块在无需求改的情况下就可以扩展。
依赖倒转其实可以说是面相对象设计的标志,用哪种语言来编写程序并不重要,如果编写时考虑的都是如何针对抽象编程而不是针对细节编程,即程序中所有的依赖关系都是终止于抽象类或者接口,那就是面相对象的设计,反之那就是过程化的设计了[ASD]。
六、装饰模式
装饰模式(Decorator):动态地给一个对象添加一些额外地职责,就增加功能来说,装饰模式比生成子类更为灵活[DP]。
Component是定义一个对象接口,可以给这些对象动态地添加职责。ConcreteComponent是定义了一个具体的对象,也可以给这个对象添加一些职责。Decorator,装饰抽象类,继承了Component,从外类来扩展Component类的功能,但对于Component来说,是无需知道Decorator的存在的。至于ConcreteDecorator就是具体的装饰对象,起到给Component添加职责的功能[DPE]。
装饰模式是利用SetComponent来对对象进行包装的。这样每个装饰对象的实现就和如何使用这个对象分开来,每个装饰对象只关心自己的功能,不需要关心如何被添加到对象链当中[DPE]。
interface Output { String output(); } class ConsoleOutput implements Output { private String input; public ConsoleOutput(String input) { this.input = input; } public String output() { return input; } } class FilterOutput implements Output { private Output component; public FilterOutput(Output output) { this.component = output; } public String output() { if (component != null) { return component.output(); } return null; } } class LowCaseOutput extends FilterOutput { public LowCaseOutput(Output output) { super(output); } @Override public String output() { return super.output().toLowerCase(); } } public class Decorator { public static void main(String args[]) { Output output = new LowCaseOutput(new ConsoleOutput("FlkJ:LKJ:LKjLKJ")); System.out.println(output.output()); } }
装饰模式是为已有功能动态添加更多功能的一种方式。
装饰模式把每个要装饰的功能放在单独的类中,并让这个类包装它所要装饰的对象,因此,当需要执行特殊行为时,客户代码就可以在运行时根据需要有选择地、按顺序地使用装饰功能包装对象类[DP]。
装饰模式的优点是把类中的装饰功能从类中搬移出去,这样可以简化原有的类。
有效地把类的核心职责和装饰功能区分开了。而且可以去除相关类中的重复的装饰逻辑。
七、代理模式
代理模式(Proxy):为其他对象提供一种代理以控制对这个对象的访问[DP]。
//同城代打 interface SameCityProxyBeat { void BeatWithBrick(); void BeatWithStick(); void BeatWithDesk(); } class Victim { private String name; public Victim(String name) { this.name = name; } public String getName() { return name; } } class Buyer implements SameCityProxyBeat { private Victim victim; public Buyer(Victim victim) { this.victim = victim; } public void BeatWithBrick() { System.out.println(String.format("用板砖打了%s一下", victim.getName())); } public void BeatWithStick() { System.out.println(String.format("用棍子打了%s一下", victim.getName())); } public void BeatWithDesk() { System.out.println(String.format("用凳子打了%s一下", victim.getName())); } } class Proxyer implements SameCityProxyBeat { private Buyer buyer; public Proxyer(Buyer buyer) { this.buyer = buyer; } public void BeatWithBrick() { buyer.BeatWithBrick(); } public void BeatWithStick() { buyer.BeatWithStick(); } public void BeatWithDesk() { buyer.BeatWithDesk(); } } public class Proxy { public static void main(String args[]) { Proxyer proxyer = new Proxyer(new Buyer(new Victim("皮皮虾"))); proxyer.BeatWithBrick(); proxyer.BeatWithStick(); proxyer.BeatWithDesk(); } }
代理模式应用场景:
1.远程代理:也就是为一个对象在不同的地址空间提供局部代表。这样可以隐藏一个对象存在于不同地址空间的事实[DP]。
2.虚拟代理:根据需要创建开销很大的对象。通过它来存放实例化需要很长时间的真实对象[DP]。
3.安全代理:用来控制真实对象访问时的权限[DP]。
4.智能指针:当调用真实的对象时,代理处理另外一些事[DP]。
代理模式其实就是在访问对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。