记住:所有的设计模式都是根据场景来使用的
面向接口编程:每个模块负责自己的职责(单一职责原则),各个模块之间通过接口进行隔离。每个模块应该承诺对外保暴露的接口是不变的,当模块内部发生变化时,其他模块是不需要知道的。这便是依赖于抽象而不依赖于实现(依赖倒置)。上层模块只需要知道下层模块暴露的接口即可,至于具体实现的细节,不需要也不应该知道(迪米特法则)。
刘备投靠了曹操,如果曹操指挥关羽,是指挥不了的,曹操只能指挥刘备,刘备再去指挥关羽,刘备就是一个接口。
工厂模式涉及到的概念:产品就是类,或者说类的实例。抽象产品就是抽象类或者接口。产品簇:有内在关系的产品,比如:KFC套餐:鸡肉堡+薯条+可乐,这三个产品就构成了一个产品簇。产品等级:
简单工厂模式:创建一个工厂类FoodFactory,里面使用switch分支,来控制产生的对象(可以是Rice或者Noodle),返回给用户的是上转型对象Food。用户想要什么对象,只需要传入的参数符合case就可以得到该对象。
如果不这样做,那么当你服务端Rice类的名字改为Rice1时,客户端的代码也需要改变。
优点(作用):把具体的产品类型从客户端代码中解耦出来,服务端如果改了具体的产品的名称,客户端不需要知道。
缺点:客户端必须记得常量和具体产品之间的关系。如果具体产品非常多,简单工厂就会变得十分臃肿。当客户端要扩展具体的产品时,势必要修改工厂类的代码,这个违背了开闭原则。
工厂方法:创建一个工厂接口FoodFactory,创建一个食物接口Food,Rice和Noodle都实现Food,然后创建RiceFactory和NoodleFactory,两者都实现Factory(返回出来的是FoodFactory),这样在RiceFactory和NoodleFactory中就获得了Rice和Noodle。我们在使用的时候,只需要创建具体的工厂(具体的工厂的返回值是FoodFactory),然后通过具体的工厂就得到了需要的对象。
当扩展的时候,我们需要实现具体的产品和具体的工厂。这样做的原因:作者在开发功能时,不会仅仅开发一些抽象产品,具体产品,对应的工厂,还会搭配一些提前做好的框架。
比如框架中有品尝方法,要是方法具有通用性,传入的参数就是FoodFactory(),此时如果我们扩展实现了凉皮类,但是没有实现具体的工厂,那么就没有办法调用这个方法。
优点:仍具有简单工厂的优点,服务端修改了具体的产品的名称客户端不知道。如果扩展,只需要实现具体的对象和对应的工厂即可。这样我们需要记工厂的名字,而且工厂的名字也有可能会改变,不过不用担心,在IT业内,工厂的名字是趋于稳定的。
缺点:如果有多个产品等级(Food,Drink),工厂类的数量就会爆炸式增长。
工厂方法的优点是在简单工厂需要扩展时展现出来的。
抽象工厂解决了工厂方法的缺点。
抽象工厂:工厂方法是一个工厂里只有一个产品等级,比如FoodFactory,不能生产Drink,抽象工厂就是Factory,里面可以{public Food getFood();public Drink getDrink()},然后再创建工厂去实现Factory,KFCFactory和CocaFactory().这样KFC就可以产生汉堡还可以产生可乐,Coca可以生产面包和汽水。这里KFC生产的汉堡+可乐就构成了一个产品簇,根据业务场景,如果需要生产肉夹馍和矿泉水,可以再来一个小吃工厂去实现Factory。
缺点:当产品等级发生变化时,可能是增加产品等级也可能删除产品等级,这样源代码就需要改变,违反了开闭原则。但是增加产品簇的话是不需要改变源代码的。当产品等级比较固定时,可以使用抽象工厂。
原型模式:主要实现就是clone。就是相当于一个模板,克隆这个模板获得一个新的对象。
克隆有浅克隆(实现Cloneable接口,重写Clone方法)和深克隆(先序列化,再进行反序列化获得对象)。
浅克隆的时候,如果被克隆的对象的某个属性还是一个对象,那么克隆出来的对象的这个属性和被克隆的对象的这个属性将指向同一块地址。但是克隆对象和被克隆对象的地址是不相同的。
浅克隆和深克隆详解浅克隆与深克隆详解与实现
建造者模式:在实例化对象之后,还要给对象赋值。
工厂模式是只new出来对象即可。
创建一个ComputerBuilder接口,接口中定义赋值的方法(装显卡,设置内存等等),并且还有一个返回Computer的方法。不同型号的电脑去实现ComputerBuilder接口,这样就不会有忘记装显卡等的问题出现,但是这样我们在获取电脑的时候,还需要在一步步的调用方法,才能进行真正的赋值。此时再创建出一个指挥者Director,在指挥者中,将ComputerBuilder传入进去,在里面去调用每一步的方法,最后将Computer返回出去。
优点:创建产品的过程稳定不变(有ComputerBuilder来保证)
创建对象的过程只写一次(有Director来保证)
当需要扩展指挥者的时候,不需要修改之前的代码,符合开闭原则。
class Computer{
private String CPU;
private String xianKa;
public String getCPU() {
return CPU;
}
public void setCPU(String CPU) {
this.CPU = CPU;
}
public String getXianKa() {
return xianKa;
}
public void setXianKa(String xianKa) {
this.xianKa = xianKa;
}
@Override
public String toString() {
return "Computer{" +
"CPU='" + CPU + '\'' +
", xianKa='" + xianKa + '\'' +
'}';
}
}
interface ComputerBuilder{
public void setCPU();
public void setXianKa();
public Computer build();
}
class AdvanceComputerBuilder implements ComputerBuilder{
private Computer computer = new Computer();
@Override
public void setCPU() {
computer.setCPU("最好的CPU");
}
@Override
public void setXianKa() {
computer.setXianKa("最好的显卡");
}
@Override
public Computer build() {
return computer;
}
}
class Director{
public Computer build(ComputerBuilder cb){
cb.setCPU();
cb.setXianKa();
return cb.build();
}
}
public class Test3 {
public static void main(String[] args) {
AdvanceComputerBuilder acb = new AdvanceComputerBuilder();
Director director = new Director();
Computer computer = director.build(acb);
System.out.println(computer);
}
}
装饰器模式:允许向现有对象添加新功能。就如向现有的饮料中添加新调料,并且不修改原有的代码。
jdk中流相关的就是使用了装饰器模式,比如字符流Reader和BufferReader。
BufferReader关联Reader是为了用Reader以前的功能再去做新功能。继承Reader是为了使自己本身还能作为Reader再传到装饰器中。
UML类图:一边继承一边关联,枸杞既能当做饮料又能当做调料传进去
模板方法设计模式:将模板设置为抽象类,需要改动的部分设置为抽象方法,当哪一个类需要使用这个模板时,只需要继承抽象模板类即可,然后重写抽象方法。
场景:计算ArrayList的执行效率,计算LinkedList的执行效率等等。
abstract class Template{
public void template(){
System.out.println("开始");
long start = System.currentTimeMillis();
code();
long end = System.currentTimeMillis();
System.out.println("结束:"+(end-start));
}
abstract void code();
}
class AB extends Template{
@Override
void code() {
ArrayList<Integer> list = new ArrayList<>();
for(int i=0;i<10000;i++){
list.add(1);
}
}
}
public class Test4 {
public static void main(String[] args) {
AB ab = new AB();
ab.template();
}
}
适配器模式:根据已有的接口生成想要的接口。