分类
设计模式分为创建型模式、结构型模式和行为型模式
创建型有五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式
结构型有七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式
行为型有十一种:策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式
创建型模式
简单的说就是创建对象用的模式。
它对类的实例化过程进行了抽象,将软件模块中对象的创建和使用分离。
简单工厂模式
严格来说它不算设计模式,它违背开闭原则。这里用来引出工厂模式。
现在有个抽象的Home类。
abstract class Home{
public abstract void size();
}
然后有两个实现类AHome和BHome
class AHome extends Home{
@Override
public void size(){
System.out.print("AHome 120");
}
}
class BHome extends Home{
@Override
public void size(){
System.out.print("BHome 80");
}
}
外界获取AHome对象和BHome对象就是通过new
Home home = new AHome();
Home home = new BHome();
如果有天需要把所有的AHome对象换成BHome对象。不可能手动去修改,因此我们需要一个类帮我们管理创建对象这个事情。这个类就是工厂类。(我感觉有点像bean的创建,有IoC容器负责创建)
class SimpleHomeFactory {
public static Home getHome(String homeType) {
switch (homeType) {
case "A": return new AHome();
case "B": return new BHome();
default: return null;
}
}
}
最后就可以通过外界传入的类型进行使用。
class Main {
public static void main(String[] args) {
Home home1 = SimpleHomeFactory.getHome("A");
home1.size();
Home homeB = SimpleHomeFactory.getHome("B");
home1.size();
}
}
class Main {
public static final String Home = "A";
public static void main(String[] args) {
Home home1 = SimpleHomeFactory.getHome(Home);
home1.size();
}
}
工厂的作用其实就是定义一个创建对象的工厂,通过工厂获取对象。
工厂方法模式
定义一个创建对象的接口,让子类决定实例化哪个产品类对象。
工厂方法使一个产品类的实例化延迟到其工厂的子类。
前面使用简单工厂的时候,有个问题,每次创建一个类,例如CHome,我需要case里添加这个判断。这个问题的本质是因为一个工厂能产生的产品太多了。那么如果我规定一个工厂只能生产一个产品,就能解决这个问题。
//共同的工厂抽象接口
interface HomeFactory{
Home creatHome();
}
//工厂的具体实现方法
class AHomeFactory implements HomeFactory{
@Override
public Home creatHome(){
return new AHome();
}
}
class BHomeFactory implements HomeFactory{
@Override
public Home creatHome(){
return new BHome();
}
}
这样的话,每个工厂都只生产自己的产品,当我们要添加新的产品时,只需要写CHomeFactory就行。外部调用的话就可以直接获取。
class Main {
public static void main(String[] args) {
HomeFactory homeFactory = new AHomeFactory();
Home home = homeFactory.creatHome();
home.size();
}
}
绕了这么一圈,最后发现要先创建一个CHome,然后再写一个CHomeFactory。好像还不如我直接new 一个。
工厂的本质就是提供了一种灵活的方式来管理对象的创建过程。
当然现实中创建一个对象也不是这个简单的创建,产品的具体创建过程是比较麻烦的,就比如房子创建出来你需要购置家具,可能放多张椅子,桌子等等问题。如果自己创建的话,就需要一个个赋值,而有了工厂就可以直接new这个工厂,让工厂去处理后续的事情。你想搬家换房,直接换一个工厂就行。
整个工厂方法出现了几个角色:
1 抽象产品:定义了产品的规范。(Home)
2 具体产品:实现了抽象产品定义的接口,由具体工厂创建,和具体工厂一一对应(AHome,BHome)
3 抽象工厂:提供了创建产品的结构,调用者通过它访问具体工厂的方法来创建产品。(HomeFactory)
4 具体工厂:抽象工厂的实现,完成具体产品的创建。(AHomeFactory,BHomeFactory)
抽象工厂模式
定义了一个接口,用于创建有依赖关系的对象族,而无需明确指明具体类。
这个也很好理解。一个房地产商,不可能只有一块地。每个地方的设计风格可能也是不同的,可能有的做住宅,有的做写字楼,有的做商业街,有的做商圈,等等。如果每个都用一个工厂方法来,可能要建十几二十个工厂类,这也不太合适。因此这个情况下,抽象工厂模式就比较合适。
它不是只生产单一的产品,而是生产一系列的产品。例如下面,产品有房子和写字楼。
abstract class Home{
public abstract void size();
}
abstract class Building{
public abstract void bsize();
}
同时有A公司的房子和写字楼。和B公司的房子和写字楼。
class AHome extends Home{
//...
}
class ABuilding extends Building {
//...
}
class BHome extends Home{
//...
}
class BBuilding extends Building {
//...
}
然后现在有个工厂既能建房子,又能建写字楼。
interface Factory{
Home createHome();
Building createBuilding();
}
然后的话A工厂和B工厂实现了这个接口
class AFactory implements Factory{
@Override
public Home createHome(){
return new AHome();
}
@Override
public Building createBuilding (){
return new ABuilding ();
}
}
class BFactory implements Factory{
@Override
public Home createHome(){
return new BHome();
}
@Override
public Building createBuilding (){
return new BBuilding ();
}
}
客户端的话就可以直接使用:
class Main {
public static void main(String[] args) {
// 切换这句代码就可以切换一套产品,由A公司切换到B公司
Factory factory = new AFactory();
// Factory factory = new BFactory();
Home home = factory.createHome();
Building building = factory.createBuilding();
home.size();
building.bsize();
}
}
当然,这也有个大的缺点,就是如果我新增一个商场,所有的AB都要生产这个产品。这肯定也是不合适的。不过当一个公司有多个产品需要一起创建的时候,这个抽象工厂模式是比较合适的。
单例模式
确保一个类只有一个实例,并提供一个全局访问点
单例的实现应该有三种方法:
1 枚举式
2 饿汉式,类加载就会创建这个单例对象
3 懒汉式,只有首次使用这个对象才会创建
枚举的方法
public Enum Singleton{
INSTANCE;
}
Class Main{
public static void main(String[] args){
Singleton intance = Singleton.INSTANCE;
}
}
饿汉式
class EagerSingleton{
private static EagerSingleton instance = new EagerSingleton();
private EagerSingleton(){}
public static EagerSingleton getInstance(){
return instance;
}
}
懒汉式,线程不安全方法
class LazySingleton{
private static LazySingleton instance;
private LazySingleton(){}
public static LazySingleton getInstance(){
if(instance==null){
instance = new LazySingleton();
}
return instance;
}
}
懒汉式,线程安全的方法
class LazySingleton{
private static volatile LazySingleton instance;
private LazySingleton(){}
public static LazySingleton getInstance(){
if(instance==null){
synchronized(LazySingleton.class){
if(instance==null){
instance = new LazySingleton();
}
}
}
return instance;
}
}
获取到单例方法还有破坏的办法。
例如序列化和反序列化。实现Serializable接口,将对象写入文件后读取,每次读取到是不同对象。但是如果单例类定义了private Object readResolve()方法,还是会返回单例。
反射:将无参构造通过反射设置为可见,然后创建对象,得到的是不同对象。但是可以通过私有构造方法进行单例对象的非空判断来解决这个问题。
原型模式
和单例相对,它可以用一个创建的实例作为原型,通过复制这个原型对象创建一个和原型对象相同的新对象。
需要先定义一个克隆接口
public interface Cloneable{
protected Object clone();
}
然后让对象类实现这个接口
class Person implements Cloneable {
private String name;
private int age;
public Person() {}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
//setter 、getter
@Override
public Person clone() throws CloneNotSupportedException {
return (Person) super.clone();
}
}
客户端的话就可以复制一个同样的。
class Main {
public static void main(String[] args) throws CloneNotSupportedException {
Person zhangsan = new Person("张三", 14);
Person zhangsanClone = zhangsan.clone();
System.out.println(zhangsan.toString());
System.out.println(zhangsanClone.toString());
}
}
这里的clone就涉及到深拷贝和浅拷贝。这里的就是浅拷贝。需要深拷贝的话要将对象写入文件后读取。
深拷贝
新对象的属性和引用都会被克隆一个,不指向原有对象的地址
浅拷贝
新对象的属性会克隆一个,引用还是指向原有对象的地址
里面出现的角色
抽象原型类:规定了原型对象必须实现clone方法。(Cloneable)
具体原型类:实现抽象原型类的clone方法,它是可被复制的对象。(Person)
访问类:使用具体原型类中clone方法来复制新对象。(Main)
建造者模式
使用多个简单的对象一步步构建一个复杂的对象,将复杂对象的构建与表示分离,使得同样的构造过程可以创建不同的表示。
简单的理解,电脑有cpu,显卡,内存。不同品牌的组合可以组成不同的主机。
class Computer {
private String audio;
private String keyboard;
private String master;
private String mouse;
private String screen;
//getter setter
}
然后有个抽象的电脑组装类
abstract class ComputerBuilder {
Computer computer = new Computer();
public Computer getComputer() {
return computer;
}
public abstract void buildAudio();
public abstract void buildKeyboard();
public abstract void buildMaster();
public abstract void buildMouse();
public abstract void buildScreen();
}
那么不同的品牌就可以使用不同的组件去组装一个电脑
class HPComputerBuilder extends ComputerBuilder{
@Override
public void buildAudio() {
computer.setAudio("hp音响");
}
@Override
public void buildKeyboard() {
computer.setKeyboard("hp键盘");
}
@Override
public void buildMaster() {
computer.setMaster("hp主机");
}
@Override
public void buildMouse() {
computer.setMouse("hp鼠标");
}
@Override
public void buildScreen() {
computer.setScreen("hp显示器");
}
}
这里其实定义了一个hp电脑建造者,它简单的将computer的每个简单对象创建。但是不可能自己组装,因此需要有个指挥者,指挥怎么组装。
class Director {
private ComputerBuilder computerBuilder;
public Director(ComputerBuilder computerBuilder) {
this.computerBuilder = computerBuilder;
}
public Computer constructComputer() {
computerBuilder.buildMaster();
computerBuilder.buildAudio();
computerBuilder.buildKeyboard();
computerBuilder.buildMouse();
computerBuilder.buildScreen();
return computerBuilder.getComputer();
}
}
我们往指挥者里传入建造者,指挥者会根据constructComputer的方法来指挥建造者组装,一般来说有顺序要求。
指挥者只负责怎么组装顺序,建造者复制具体的组装
在主线程去获取一个电脑
class Main {
public static void main(String[] args) {
// 指挥者传入不同建造者即可建造同一复杂产品的不同组合
Director director = new Director(new HPComputerBuilder());
Computer computer = director.constructComputer();
System.out.println(computer.toString());
}
}
如果想新增一个组合,只需要新增一个建造者。然后往指挥者中传入这个对象,就可以了。
优点:
封装性很好,一般产品类和建造者类比较稳定。客户不需要知道内部组成细节,只需要知道这个建造者可以建造就行。易拓展。
缺点:
如果产品之间差异很大,就不适用建造者模式。
里面出现的角色
抽象建造者类:这个接口规定实现复杂对象的创建,不涉及具体的部件。(ComputerBuider)
具体建造者类:实现Builder接口,完成复杂产品各个部件的具体创建方法。(HPComputerBuilder)
产品类:要创建的复杂对象。(Computer)
指挥者类:调用具体建造者创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建、或按顺序创建。(Director)
简单的说:一个复杂对象有多个简单对象,我搞个建造者,这个建造者来创建每个简单对象,再来个指挥者,指挥者来把这些简单对象按照顺序来组合成一个复杂对象
工厂模式和建造者模式的差异
工厂方法模式注重的是整体对象的创建方式;而建造者模式注重的是部件构建的过程,意在通过一步一步地精确构造创建出一个复杂的对象。
我们举个简单例子来说明两者的差异,如要制造一个超人,如果使用工厂方法模式,直接产生出来的就是一个力大无穷、能够飞翔、内裤外穿的超人;而如果使用建造者模式,则需要组装手、头、脚、躯干等部分,然后再把内裤外穿,于是一个超人就诞生了。