设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类编目的代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。设计模式使代码编写真正工程化,设计模式是软件工程的基石,如同大厦的设计图一样。项目中合理的运用设计模式可以完美的解决很多问题,每种模式都有相应的原理来与之对应,每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因。
一、设计模式的六大原则
1、开闭原则(Open Close Principle)
开闭原则就是说:对扩展开放,对修改关闭。在程序需要进行扩展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。
2、依赖倒转原则(Dependence Inversion Principle)
依赖倒转原则就是说:针对接口编程,依赖于抽象而不依赖于具体。它是开闭原则的基础。
3、里氏代换原则(Liskov Substitution Principle)
里氏代换原则就是说:任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
4、接口隔离原则(Interface Segregation Principle)
接口隔离原则就是说:使用多个隔离的接口,比使用单个接口要好。其实也就是降低类之间的耦合度的意思。
5、合成复用原则(Composite Reuse Principle)
合成复用原则就是说:尽量使用合成/聚合的方式,而不是使用继承。
6、迪米特原则(最少知道原则)(Demeter Principle)
迪米特原则就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。
二、设计模式的分类与应用
总体来说设计模式分为三大类(23种):
创建型模式(5种):工厂模式、抽象工厂模式、单例模式、建造者模式、原型模式。
为什么要创建对象?无非有三种原因:
①其它地方需要使用该对象;
②需要使用该对象中的变量;
③需要使用该对象中的方法。
创建对象的常见方式有:
①使用new关键字;
②通过反射机制创建对象;
③通过实现Cloneable接口实现clone()方法;
④通过工厂类创建对象。
如果使用最常用的new关键字创建对象:
bean:
public class User {
private String userName;
private String passWord;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassWord() {
return passWord;
}
public void setPassWord(String passWord) {
this.passWord = passWord;
}
}
调用类:
public class Login {
User user = new User();
public boolean login(String userName,String passWord){
if(userName.equals(user.getUserName()) && passWord.equals(user.getPassWord()))
return true;
else
return false;
}
}
假设在Login中需要创建另一个User类型的对象,那么就必须要修改Login类,因为User对象的创建和使用耦合在了Login类中。但工厂模式可以解决这个问题,将User的创建让工厂类来做。这样工厂类只负责对象的创建,对象创建发生变化时只需修改工厂类即可,而Login类只负责对象的使用,对象的行为(方法)发生变化时只需要修改Login类即可。
1、工厂模式:提供创建少量对象作为成员变量的方法的模式。与策略模式相比,此模式注重的是结果,相当于一个黑盒。分2种:
1>简单工厂模式:定义一个工厂类,它可以根据不同的参数返回不同类的实例。被创建的实例通常都具有共同的父类。因为在简单工厂模式中用于创建实例的方法是静态(static)方法,因此简单工厂模式又被称为静态工厂方法模式。此模式由3部分组成:
①抽象产品类:它是工厂类所创建的所有对象的父类,封装了各种产品对象的公有方法,它的引入将提高系统的灵活性,使得在工厂类中只需定义一个通用的工厂方法,因为所有创建的具体产品对象都是其子类对象。
public abstract class Car {
//可以行驶的属性
public void drive(){
System.out.println("车是可以行驶的");
};
//车的特性
public abstract void identity();
}
②具体产品类:它是简单工厂模式的创建目标,所有被创建的对象都充当这个角色的某个具体类的实例。每一个具体产品类都继承了抽象产品类,需要实现在抽象产品类中声明的抽象方法。
public class Ferrari extends Car {
public void identity() {
System.out.println("这是法拉利的特性");
}
}
public class Lamborghini extends Car {
public void identity() {
System.out.println("这是兰博基尼的特性");
}
}
③工厂类:它是简单工厂模式的核心,负责实现创建所有产品实例的内部逻辑;在工厂类中提供静态的工厂方法,创建所需的产品对象时外界可以直接调用。
public class CarFactory {
public static Car car;
public static void produce(int n){
if(n==1){
car = new Ferrari();
System.out.println("制造出一辆法拉利");
}else if(n==2){
car = new Lamborghini();
System.out.println("制造出一辆兰博基尼");
}else{
car = null;
System.out.println("该工厂不制造其他汽车");
}
}
}
主入口:
public class Test {
public static void main(String[] args) {
CarFactory.produce(1);
if(CarFactory.car!=null){
CarFactory.car.drive();
CarFactory.car.identity();
}
CarFactory.produce(2);
if(CarFactory.car!=null){
CarFactory.car.drive();
CarFactory.car.identity();
}
CarFactory.produce(3);
if(CarFactory.car!=null){
CarFactory.car.drive();
CarFactory.car.identity();
}
}
}
可以将简单工厂模式进行简化,将抽象产品类和工厂类进行合并,将工厂类的静态方法放在抽象产品类中。
2>工厂方法模式:简单工厂模式存在一个问题,当系统中需要引入新产品时必定要修改工厂类的源代码,将违背“开闭原则”,由此引入工厂方法模式。定义若干个工厂类,它们有同一个抽象工厂父类;另外定义若干个具体产品类,它们具有共同的抽象产品父类。此模式由4部分组成:
①抽象产品类:它是工厂类所创建的所有对象的父类,封装了各种产品对象的公有方法。
public abstract class Car {
//可以行驶的属性
public void drive(){
System.out.println("车是可以行驶的");
};
//车的特性
public abstract void identity();
}
②具体产品类:每一个具体产品类都继承了抽象产品类,每一个具体产品类都对应一个具体的工厂类。
public class Ferrari extends Car {
public void identity() {
System.out.println("这是法拉利的特性");
}
}
public class Lamborghini extends Car {
public void identity() {
System.out.println("这是兰博基尼的特性");
}
}
③抽象工厂类:含有返回产品的工厂方法,所有实现抽象工厂类的子类都必须实现该方法。
public interface CarFactory {
public Car produceCar();
}
④具体工厂类:它是抽象工厂类的子类,实现了抽象工厂类中定义的工厂方法,返回一个具体产品类的实例。
public class FerrariFactory implements CarFactory{
public Car produceCar(){
System.out.println("制造出一辆法拉利");
return new Ferrari();
}
}
public class LamborghiniFactory implements CarFactory{
public Car produceCar(){
System.out.println("制造出一辆兰博基尼");
return new Lamborghini();
}
}
主入口:
public class Test {
public static void main(String[] args) {
CarFactory ferrariFactory = new FerrariFactory();
Car ferrari = ferrariFactory.produceCar();
ferrari.drive();
ferrari.identity();
CarFactory lamborghiniFactory = new LamborghiniFactory();
Car lamborghini = lamborghiniFactory.produceCar();
lamborghini.drive();
lamborghini.identity();
}
}
抽象工厂类中的工厂方法可以使用不同的入参实现重载来适用多种不同的业务场合。
2、抽象工厂模式:当工厂模式创建的成员变量含有产品等级结构和产品族时,就需要使用抽象工厂模式。
什么是产品等级结构?并列的多个抽象类所衍生出的具体类的关系,也就是不同父类的子类的对象之间的关系结构,是横向维度。
什么是产品族?由继承产生的一对多关系结构,也就是父类与具体子类之间的关系结构,是纵向维度。
此模式由4部分组成:
①抽象产品类:它是工厂类所创建的各对象的父类,每种产品都有各自的一个父类。
public abstract class Car {
//可以行驶的属性
public void drive(){
System.out.println("车是可以行驶的");
};
//车的特性
public abstract void identity();
}
public abstract class Aircraft {
//可以飞行的属性
public void fly(){
System.out.println("飞行器是可以飞行的");
};
//飞行器的特性
public abstract void identity();
}
②具体产品类:每一个具体产品类都继承了抽象产品类,每个具体产品类与抽象产品类形成产品族,不同类的具体产品类之间形成产品等级结构。
public class Ferrari extends Car {
public void identity() {
System.out.println("这是法拉利的特性");
}
}
public class Boeing extends Aircraft {
public void identity() {
System.out.println("这是波音的特性");
}
}
③抽象工厂类:含有多个工厂方法,每个工厂方法返回某一类型的产品,实现抽象工厂类的子类都必须实现这些方法。
public abstract class Factory {
public abstract Car produceCar();
public abstract Aircraft produceAircraft();
}
④具体工厂类:它是抽象工厂类的子类,实现了抽象工厂类中定义的每个工厂方法,每个工厂方法都返回某种抽象产品类的实例。
public class MachineFactory extends Factory{
public Car produceCar(){
System.out.println("制造出一辆车");
return new Ferrari();
}
public Aircraft produceAircraft() {
System.out.println("制造出一台飞行器");
return new Boeing();
}
}
主入口:
public class Test {
public static void main(String[] args) {
Factory factory = new MachineFactory();
Car ferrari = factory.produceCar();
ferrari.drive();
ferrari.identity();
Aircraft aircraft = factory.produceAircraft();
aircraft.fly();
aircraft.identity();
}
}
3、单例模式:某些情况下需要限制类只能创建唯一的一个实例而不允许创建多个实例。单例模式能保证某个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类。单例类的特点是:
①只能有一个实例,也就是说不能在单例类外部使用new构造方法的方式实例化对象,所以构造方法必须是private的;
②必须自行创建这个实例,外部不能使用new实例化对象,那么只能在内部实例化。而实例化时希望是与类有关的且是唯一的,那么本类的对象作为本类的一个成员变量应该是静态(static)的;
③必须自行向整个系统提供这个实例,所以将该静态的本类的对象设为private,但访问对象的方法设为public static的。
单例模式的实现方式有2种:
1>饿汉式单例类:是典型的空间换时间,当类装载的时候就会创建类的静态实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断,节省了运行时间。也不会因为instance = new LazySingleton()语句是分两步执行的而出现创建多个单例对象的情况,可确保单例对象的唯一性。
public class EagerSingleton {
private static EagerSingleton instance = new EagerSingleton();
private EagerSingleton(){}
public static EagerSingleton getInstance(){
return instance;
}
}
2>懒汉式单例类:考虑到instance = new LazySingleton()语句是分两步执行、synchronized加锁性能低以及volatile对JDK代码优化的屏蔽,所以使用静态内部类维护单例。
public class LazySingleton {
public static LazySingleton getInstance() {
return LazySingletonHolder.instance;
}
private static class LazySingletonHolder {
private static LazySingleton instance = new LazySingleton();
}
private LazySingleton() {
}
}
或者使用单元素的枚举实现单例
public enum LazySingleton {
instance
}
4、建造者模式:使用相同的构建过程一步步构建包含多个组件的不同复杂对象。此模式由4部分组成:
①产品类:就是要构建的复杂对象,它包含多个组部。具体创建者类会调用该类中的方法用来组装该对象。
public class Car{
private String part1;
private String part2;
private String part3;
public String getPart1() {
return part1;
}
public void setPart1(String part1) {
this.part1 = part1;
}
public String getPart2() {
return part2;
}
public void setPart2(String part2) {
this.part2 = part2;
}
public String getPart3() {
return part3;
}
public void setPart3(String part3) {
this.part3 = part3;
}
}
②抽象建造者类:为创建一个产品类对象的各个组件指定抽象接口,通常会持有产品类对象,另外一般会声明两类方法,一类方法是buildPartX()用于创建复杂对象的各个组件;另一类方法返回复杂对象。它既可以是抽象类,也可以是接口。
public abstract class Builder {
protected Car car = new Car();
public abstract void buildPart1(String str);
public abstract void buildPart2(String str);
public abstract void buildPart3(String str);
public Car getCar(){
return this.car;
}
}
③具体建造者类:是抽象建造者类的实现,每建造一种新类型的产品就需要创建一种新的具体建造者类实现抽象建造者类。具体建造者类的方法中可按一定的顺序调用产品类中的方法添加组件。
public class FerrariBuilder extends Builder{
public void buildPart1(String str) {
car.setPart1(str);
System.out.println("安装法拉利的部件1:"+str);
}
public void buildPart2(String str) {
car.setPart2(str);
System.out.println("安装法拉利的部件2:"+str);
}
public void buildPart3(String str) {
car.setPart3(str);
System.out.println("安装法拉利的部件3:"+str);
}
}
public class LamborghiniBuilder extends Builder{
public void buildPart1(String str) {
car.setPart1(str);
System.out.println("安装兰博基尼的部件1:"+str);
}
public void buildPart2(String str) {
car.setPart2(str);
System.out.println("安装兰博基尼的部件2:"+str);
}
public void buildPart3(String str) {
car.setPart3(str);
System.out.println("安装兰博基尼的部件3:"+str);
}
}
④指挥类:通过持有具体的建造者类之后,通过具体建造者类的方法按一定的顺序间接调用产品类中的各组件的方法。
public class Director {
private Builder factory;
public Director(Builder factory){
this.factory=factory;
}
public Car produce(){
factory.buildPart1("发动机");
factory.buildPart2("车轮");
factory.buildPart3("车体");
return factory.getCar();
}
}
主入口:
public class Test {
public static void main(String[] args) {
Builder factory1 = new FerrariBuilder();
Director director1 = new Director(factory1);
Car car1 =director1.produce();
System.out.println(car1.getPart1());
System.out.println(car1.getPart2());
System.out.println(car1.getPart3());
Builder factory2 = new LamborghiniBuilder();
Director director2 = new Director(factory2);
Car car2 =director2.produce();
System.out.println(car2.getPart1());
System.out.println(car2.getPart2());
System.out.println(car2.getPart3());
}
}
可以将创建者模式进行简化,将抽象创建者类和指挥类进行合并,将指挥类的方法以静态方法的形式合并到抽象建造者类中。
5、原型模式:就是将一个对象作为原型,对其进行复制、克隆,产生一个和原对象类似的新对象。原型模式的核心是如何实现克隆的方法,常用的方式有2种:
1>通用实现方式:在类中定义一个克隆方法,在该方法中创建一个该类的实例,然后将调用此克隆方法的对象中的属性赋值到此新创建的实例中然后返回。
public class Prototype {
private String str;
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
public Prototype clone(){
Prototype prototype = new Prototype();
prototype.str=this.str;
return prototype;
}
}
2>实现Cloneable标记接口的类的父类Object的clone()方法:如果一个类没有实现Cloneable接口但是调用了clone()方法,Java编译器将抛出一个CloneNotSupportedException异常。
浅克隆:如果原型对象的成员变量是基本类型,浅克隆时将基本类型的成员变量复制一份给克隆对象;如果原型对象的成员变量是引用类型,浅克隆时将克隆对象的成员变量指向原型对象的引用类型的成员变量,也就是说克隆对象中引用类型的成员变量和原型对象中引用类型的成员变量的内存地址是同一个。
深克隆:对象的成员变量无论是基本类型还是引用类型,成员变量都会复制分给克隆对象,也就是说克隆对象中所有的成员变量和原型对象中所有的成员变量的值虽然相同,但属于不同的内存空间。要实现深克隆,通常的做法是使用序列化,也就是将要克隆的对象写到IO流中并从IO流中读出来,要实现序列化,必须实现Serializable标记接口。
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class Prototype implements Cloneable,Serializable{
private static final long serialVersionUID = 1L;
private int count;
private String str=new String();
//浅度克隆
public Prototype clone() throws CloneNotSupportedException{
Prototype prototype = (Prototype)super.clone();
return prototype;
}
//深度克隆
public Prototype deepClone() throws IOException,ClassNotFoundException{
/* 写入当前对象的二进制流(序列化) */
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
/* 读出二进制流产生的新对象(反序列化) */
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (Prototype)ois.readObject();
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
}
主入口:
import java.io.IOException;
public class Test {
public static void main(String args[]){
Prototype prototype = new Prototype();
Prototype prototype1 = null;
Prototype prototype2 = null;
try {
prototype1 = prototype.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
try {
prototype2 = prototype.deepClone();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(prototype==prototype1); //产生副本
System.out.println(prototype.getCount()==prototype1.getCount()); //浅度克隆产生副本,但因为是基本类型,==比较的是值
System.out.println(prototype.getStr()==prototype1.getStr()); //浅度克隆不产生副本,只是引用地址相同
System.out.println(prototype==prototype2); //产生副本
System.out.println(prototype.getCount()==prototype2.getCount()); //深度克隆产生副本,但因为是基本类型,==比较的是值
System.out.println(prototype.getStr()==prototype2.getStr()); //深度克隆产生副本
}
}