一、设计模式的原则
1.单一职责原则
- 一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。
- 单一职责原则是实现高内聚、低耦合的指导方针,它是最简单但又最难运用的原则,需要设计人员发现类的不同职责并将其分离,而发现类的多重职责需要设计人员具有较强的分析设计能力和相关实践经验。
2.开闭原则
- 一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。
- 抽象化是开闭原则的关键。可以为系统定义一个相对稳定的抽象层,而将不同的实现行为移至具体的实现层中完成。
3.里氏替换原则
- 所有引用基类(父类)的地方必须能透明地使用其子类的对象。
- 在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类对象的话,那么它不一定能够使用基类对象。
- 里氏代换原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。
4.依赖倒置原则
- 抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程
- 在实现依赖倒转原则时,我们需要针对抽象层编程,而将具体类的对象通过依赖注入(DependencyInjection, DI)的方式注入到其他对象中,依赖注入是指当一个对象要与其他对象发生依赖关系时,通过抽象来注入所依赖的对象。常用的注入方式有三种,分别是:构造注入,设值注入(Setter注入)和接口注入。构造注入是指通过构造函数来传入具体类的对象,设值注入是指通过Setter方法来传入具体类的对象,而接口注入是指通过在接口中声明的业务方法来传入具体类的对象。这些方法在定义时使用的是抽象类型,在运行时再传入具体类型的对象,由子类对象来覆盖父类对象。
- 开闭原则是目标,里氏代换原则是基础,依赖倒转原则是手段
5.接口隔离原则
- 使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。
6.合成复用原则
- 尽量使用对象组合,而不是继承来达到复用的目的。
- 通过继承来进行复用的主要问题在于继承复用会破坏系统的封装性,因为继承会将基类的实现细节暴露给子类,由于基类的内部细节通常对子类来说是可见的,所以这种复用又称“白箱”复用,如果基类发生改变,那么子类的实现也不得不发生改变;从基类继承而来的实现是静态的,不可能在运行时发生改变,没有足够的灵活性
7.迪米特法则
- 一个软件实体应当尽可能少地与其他实体发生相互作用
- 应该尽量减少对象之间的交互,如果两个对象之间不必彼此直接通信,那么这两个对象就不应当发生任何直接的相互作用,如果其中的一个对象需要调用另一个对象的某一个方法的话,可以通过第三者转发这个调用。简言之,就是通过引入一个合理的第三者来降低现有对象之间的耦合度。
二、设计模式的分类
- 创建型模式(5种):工厂方法模式,抽象工厂模式,建造者模式,原型模式,单例模式。
- 结构型模式(7种):适配器模式,装饰器模式,代理模式,外观模式,桥接模式,组合模式,享元模式。
- 行为型模式(11种):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
创建型模式
1. 工厂模式(Factory Method)
interface food{}
class A implements food{}
class B implements food{}
class C implements food{}
public class StaticFactory {
private StaticFactory(){}
public static food getA(){ return new A(); }
public static food getB(){ return new B(); }
public static food getC(){ return new C(); }
}
class Client{
//客户端代码只需要将相应的参数传入即可得到对象
//用户不需要了解工厂类内部的逻辑。
public void get(String name){
food x = null ;
if ( name.equals("A")) {
x = StaticFactory.getA();
}else if ( name.equals("B")){
x = StaticFactory.getB();
}else {
x = StaticFactory.getC();
}
}
}
2. 抽象工厂模式(Abstract Factory)
- 一个基础接口定义了功能,每个实现接口的子类就是产品,然后定义一个工厂接口,实现了工厂接口的就是工厂,这时候,接口编程的优点就出现了,我们可以新增产品类(只需要实现产品接口),只需要同时新增一个工厂类,客户端就可以轻松调用新产品的代码。
- 抽象工厂的灵活性就体现在这里,无需改动原有的代码,毕竟对于客户端来说,静态工厂模式在不改动StaticFactory类的代码时无法新增产品,如果采用了抽象工厂模式,就可以轻松的新增拓展类。
interface food{}
class A implements food{}
class B implements food{}
interface produce{ food get();}
class FactoryForA implements produce{
@Override
public food get() {
return new A();
}
}
class FactoryForB implements produce{
@Override
public food get() {
return new B();
}
}
public class AbstractFactory {
public void ClientCode(String name){
food x= new FactoryForA().get();
x = new FactoryForB().get();
}
}
3. 建造者模式(Builder)
- 当一个类的构造函数参数个数超过4个,而且这些参数有些是可选的参数,考虑使用构造者模式。
public class Builder {
static class Student{
String name = null ;
int number = -1 ;
String sex = null ;
int age = -1 ;
String school = null ;
//构建器,利用构建器作为参数来构建Student对象
static class StudentBuilder{
String name = null ;
int number = -1 ;
String sex = null ;
int age = -1 ;
String school = null ;
public StudentBuilder setName(String name) {
this.name = name;
return this ;
}
public StudentBuilder setNumber(int number) {
this.number = number;
return this ;
}
public StudentBuilder setSex(String sex) {
this.sex = sex;
return this ;
}
public StudentBuilder setAge(int age) {
this.age = age;
return this ;
}
public StudentBuilder setSchool(String school) {
this.school = school;
return this ;
}
public Student build() {
return new Student(this);
}
}
public Student(StudentBuilder builder){
this.age = builder.age;
this.name = builder.name;
this.number = builder.number;
this.school = builder.school ;
this.sex = builder.sex ;
}
}
public static void main( String[] args ){
Student a = new Student.StudentBuilder().setAge(13).setName("LiHua").build();
Student b = new Student.StudentBuilder().setSchool("sc").setSex("Male").setName("ZhangSan").build();
}
}
4. 原型模式(Protype)
- 在原型模式中我们可以利用过一个原型对象来指明我们所要创建对象的类型,然后通过复制这个对象的方法来获得与该对象一模一样的对象实例。
- 深拷贝、浅拷贝
深拷贝
public class Prototype implements Cloneable{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
protected Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}finally {
return null;
}
}
public static void main ( String[] args){
Prototype pro = new Prototype();
Prototype pro1 = (Prototype)pro.clone();
}
}
- Object类提供了一个clone()方法,该方法可以将一个java对象复制一份,因此在java中可以直接使用clone()方法来复制一个对象。但是需要实现clone的Java类必须要实现一个接口:Cloneable.该接口表示该类能够复制且具体复制的能力,如果不实现该接口而直接调用clone()方法会抛出CloneNotSupportedException异常。
5. 单例模式(Singleton)
1). 饿汉法
- 在第一次引用该类的时候就创建对象实例,而不管实际是否需要创建。
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
2). 单线程写法
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
3). 考虑线程安全的写法
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
4). 兼顾线程安全和效率的写法
双重校验锁
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
5). 静态内部类法
//Initialization on Demand Holder
public class Singleton{
private Singleton(){
}
private static class HolderClass{
private final static Singleton instance = new Singleton();
}
public static Singleton getInstance(){
return HolderClass.instance;
}
public static void main(String args[]){
Singleton s1, s2;
s1 = Singleton.getInstance();
s2 = Singleton.getInstance();
System.out.println(s1==s2);
}
}
- 由于静态单例对象没有作为Singleton的成员变量直接实例化,因此类加载时不会实例化Singleton,第一次调用getInstance()时将加载内部类HolderClass,在该内部类中定义了一个static类型的变量instance,此时会首先初始化这个成员变量,由Java虚拟机来保证其线程安全性,确保该成员变量只能初始化一次。由于getInstance()方法没有任何线程锁定,因此其性能不会造成任何影响。
- 通过使用IoDH,我们既可以实现延迟加载,又可以保证线程安全,不影响系统性能,不失为一种最好的Java语言单例模式实现方式(其缺点是与编程语言本身的特性相关,很多面向对象语言不支持IoDH)
6). 枚举写法
- 这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
结构型模式
1. 适配器模式(Adapter)
- 适配器就是一种适配中间件,它存在于不匹配的二者之间,用于连接二者,将不匹配变得匹配,简单点理解就是平常所见的转接头,转换器之类的存在。
1). 类适配器模式
- 当我们要访问的接口A中没有我们想要的方法 ,却在另一个接口B中发现了合适的方法,我们又不能改变访问接口A
- 原理:通过继承来实现适配器功能。
public interface Ps2 {
void isPs2();
}
public interface Usb {
void isUsb();
}
public class Usber implements Usb {
@Override
public void isUsb() {
System.out.println("USB口");
}
}
public class Adapter extends Usber implements Ps2 {
@Override
public void isPs2() {
isUsb();
}
}
public class Clienter {
public static void main(String[] args) {
Ps2 p = new Adapter();
p.isPs2();
}
}
2). 对象适配
- 原理:通过组合来实现适配器功能。
public class Adapter implements Ps2 {
private Usb usb;
public Adapter(Usb usb){
this.usb = usb;
}
@Override
public void isPs2() {
usb.isUsb();
}
}
public class Clienter {
public static void main(String[] args) {
Ps2 p = new Adapter(new Usber());
p.isUsb();
}
}
3). 接口适配器模式
- 当存在这样一个接口,其中定义了N多的方法,而我们现在却只想使用其中的一个到几个方法,如果我们直接实现接口,那么我们要对所有的方法进行实现,会导致这个类变得臃肿,调用也不方便,这时我们可以使用一个抽象类作为中间件,即适配器,用这个抽象类实现接口,而在抽象类中所有的方法都进行置空,那么我们在创建抽象类的继承类,而且重写我们需要使用的那几个方法即可。
- 原理:通过抽象类来实现适配
public interface A {
void a();
void b();
void c();
void d();
void e();
void f();
}
public abstract class Adapter implements A {
public void a(){}
public void b(){}
public void c(){}
public void d(){}
public void e(){}
public void f(){}
}
public class Ashili extends Adapter {
public void a(){
System.out.println("实现A方法被调用");
}
public void d(){
System.out.println("实现d方法被调用");
}
}
public class Clienter {
public static void main(String[] args) {
A a = new Ashili();
a.a();
a.d();
}
}
2.装饰模式
- 装饰器模式在不影响各个ConcreteComponent核心价值的同时,添加了他特有的装饰效果,具备非常好的通用性
//基础接口
public interface Component {
public void biu();
}
//具体实现类
public class ConcretComponent implements Component {
public void biu() {
System.out.println("biubiubiu");
}
}
//装饰类
public class Decorator implements Component {
public Component component;
public Decorator(Component component) {
this.component = component;
}
public void biu() {
this.component.biu();
}
}
//具体装饰类
public class ConcreteDecorator extends Decorator {
public ConcreteDecorator(Component component) {
super(component);
}
public void biu() {
System.out.println("ready?go!");
this.component.biu();
}
}
//使用装饰器
Component component = new ConcreteDecorator(new ConcretComponent());
component.biu();
//console:
ready?go!
biubiubiu
3. 代理模式
- 通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.
- 这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法
- 代理模式的关键点是:代理对象与目标对象.代理对象是对目标对象的扩展,并会调用目标对象
1).静态代理
- 静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类.
/**
* 接口
*/
public interface IUserDao {
void save();
}
/**
* 接口实现
* 目标对象
*/
public class UserDao implements IUserDao {
public void save() {
System.out.println("----已经保存数据!----");
}
}
/**
* 代理对象,静态代理
*/
public class UserDaoProxy implements IUserDao{
//接收保存目标对象
private IUserDao target;
public UserDaoProxy(IUserDao target){
this.target=target;
}
public void save() {
System.out.println("开始事务...");
target.save();//执行目标对象的方法
System.out.println("提交事务...");
}
}
/**
* 测试类
*/
public class App {
public static void main(String[] args) {
//目标对象
UserDao target = new UserDao();
//代理对象,把目标对象传给代理对象,建立代理关系
UserDaoProxy proxy = new UserDaoProxy(target);
proxy.save();//执行的是代理的方法
}
}
- 可以做到在不修改目标对象的功能前提下,对目标功能扩展。
- 缺点: 因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护.
2). 动态代理
- 特点:
1>. 代理对象,不需要实现接口
2>. 代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)
3>. 动态代理也叫做:JDK代理,接口代理
/**
* 创建动态代理对象
* 动态代理不需要实现接口,但是需要指定接口类型
*/
public class ProxyFactory{
//维护一个目标对象
private Object target;
public ProxyFactory(Object target){
this.target=target;
}
//给目标对象生成代理对象
public Object getProxyInstance(){
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始事务2");
//执行目标对象方法
Object returnValue = method.invoke(target, args);
System.out.println("提交事务2");
return returnValue;
}
}
);
}
}
/**
* 测试类
*/
public class App {
public static void main(String[] args) {
// 目标对象
IUserDao target = new UserDao();
// 【原始的类型 class cn.itcast.b_dynamic.UserDao】
System.out.println(target.getClass());
// 给目标对象,创建代理对象
IUserDao proxy = (IUserDao) new ProxyFactory(target).getProxyInstance();
// class $Proxy0 内存中动态生成的代理对象
System.out.println(proxy.getClass());
// 执行方法 【代理对象】
proxy.save();
}
}
行为型模式
1. 模板方法模式
- 模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤的实现延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中某些步骤的具体实现。
- 注意几点:
- 保护抽象类中定义算法顺序的方法不被子类修改。
- 分离可变及不可变部分,让子类自己决定可变部分的实现。
- 让算法的具体实现对子类开放,对其他类关闭。
package com.singland.dp.template;
public abstract class DriveTemplate {
public final void drive() {
openDoor();
startEngine();
gear();
go();
if (music()) {
mp3();
}
brake();
stop();
}
protected abstract void openDoor();
protected void startEngine() {
System.out.println("engine started !");
}
protected abstract void gear();
protected void go() {
System.out.println("running...");
}
private void mp3() {
System.out.println("music is good");
}
protected boolean music() {
return false;
}
protected abstract void brake();
protected void stop() {
System.out.println("stopped !");
}
}
package com.singland.dp.template;
public class SuzukiScross extends DriveTemplate {
@Override
protected void openDoor() {
System.out.println("keyless entry");
}
@Override
protected void gear() {
System.out.println("gear with hand");
}
@Override
protected void brake() {
System.out.println("brake with foot");
}
@Override
protected boolean music() {
return true;
}
}
- JDBC代码操作数据库
package com.studentinfomgt.dao;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
public abstract class JdbcTemplate2 {
@Autowired
private DataSource dataSource;
private Connection connection;
protected PreparedStatement statement;
protected ResultSet resultSet;
public final void dbOperation(String sql, Object entity) throws SQLException {
getStatement(sql);
crud(entity);
releaseResources();
}
protected void getStatement(String sql) throws SQLException {
connection = dataSource.getConnection();
this.statement = connection.prepareStatement(sql);
}
protected abstract void crud(Object entity) throws SQLException;
private void releaseResources() throws SQLException {
if (resultSet != null)
resultSet.close();
if (statement != null)
statement.close();
if (connection != null)
connection.close();
}
}
package com.studentinfomgt.dao;
import java.sql.SQLException;
import com.studentinfomgt.pojo.Student;
public class JdbcCreateEntity extends JdbcTemplate2 {
@Override
protected void crud(Object entity) throws SQLException {
Student student = (Student) entity;
statement.setString(1, student.getId());
statement.setString(2, student.getStudentNumber());
statement.setString(3, student.getFirstName());
statement.setString(4, student.getLastName());
statement.setString(5, student.getGender());
statement.setInt(6, student.getAge());
statement.setString(7, student.getClassName());
statement.setString(8, student.getMajor());
statement.execute();
}
}
2. 访问者模式
- 将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。
- 访问者中的元素:
- 抽象访问者(Visitor)角色:定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素。
- 具体访问者(ConcreteVisitor)角色:实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。
- 抽象元素(Element)角色:声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept() 方法的参数。
- 具体元素(ConcreteElement)角色:实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作。
/**
* 访问者接口
*/
public interface IVisitor {
void visit(FreeCourse freeCourse);
void visit(CodingCourse codingCourse);
}
/**
* 访问者的具体实现
*/
public class Visitor implements IVisitor {
@Override
public void visit(FreeCourse freeCourse) {
System.out.println("free course: " + freeCourse.getName());
}
@Override
public void visit(CodingCourse codingCourse) {
System.out.println("coding course: " + codingCourse.getName() + " price: " + codingCourse.getPrice());
}
}
/**
* 抽象类,课程
*/
public abstract class Course {
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
abstract void accept(IVisitor visitor);
}
/**
* 具体的实体类,免费课程
*/
public class FreeCourse extends Course {
@Override
void accept(IVisitor visitor) {
visitor.visit(this);
}
}
/**
* 具体的实现类,实战课程
*/
public class CodingCourse extends Course {
private int price;
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
@Override
void accept(IVisitor visitor) {
visitor.visit(this);
}
}
/**
* 测试类
*/
public class App {
@Test
public void test(){
//初始化容器
List<Course> courses = new ArrayList<>();
//构建课程实现
FreeCourse freeCourse = new FreeCourse();
freeCourse.setName("SpringMVC.");
CodingCourse codingCourse = new CodingCourse();
codingCourse.setName("Java design pattern.");
codingCourse.setPrice(299);
//装填
courses.add(freeCourse);
courses.add(codingCourse);
//访问
for (Course course : courses) {
course.accept(new Visitor());
}
}
}
- 优点:
- 扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
- 复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
- 符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。
- 缺点:
- 增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
- 破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性,具体元素变更比较麻烦。违反了依赖倒置原则。
- 访问者模式依赖了具体类,而没有依赖抽象类。