设计模式
设计模式七大原则、类之间关系https://blog.csdn.net/qq_42432141/article/details/107960977
23种设计模式
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
其实还有两类:并发型模式和线程池模式。用一个图片来整体描述一下:
单例模式(8种)
什么是单例模式?
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例, 并且该类只提供一个取得其对象实例的方法(静态方法)。
饿汉式
饿汉式特点
一加载类,就实例化对象
饿汉式(静态常量)
// 饿汉式 1
class EagerSingleton {
// 1、构造器私有化,外部只能new
private EagerSingleton() {
}
// 2、本类内部创建对象实例
private final static EagerSingleton INSTANCE = new EagerSingleton();
// 3、对外提供一个共有的静态方法,返回实例对象
public static EagerSingleton getInstance() {
return INSTANCE;
}
}
饿汉式(静态代码块)
// 饿汉式 2
class EagerSingleton {
// 构造器私有化,外部只能new
private EagerSingleton() {
}
// 2、本类内部创建对象实例
private static EagerSingleton instance;
static { // 在静态代码块中,创建单例对象
instance = new EagerSingleton();
}
// 3、对外提供一个共有的静态方法,返回实例对象
public static EagerSingleton getInstance() {
return instance;
}
}
优缺点
- 优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
- 缺点:在类装载的时候就完成实例化,没有达到 Lazy Loading 的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费
- 这种方式基于 classloder 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,在单例模式中大多数都是调用 getInstance 方法, 但是导致类装载的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 就没有达到 lazy loading 的效果
懒汉式
懒汉式特点
什么时候使用,什么时候加载
懒汉式(线程不安全)
/**
* 懒汉式 1
* 线程不安全
* 在多线程下,多个进程同时进入 if (instance == null)判断语句块的时候
* 就会创建多个实例
*/
class LazySingleton {
private static LazySingleton instance;
public LazySingleton() {
}
/**
* 提供一个静态的共有方法,当使用到该方法的时候,采取创建instance
* @return 返回创建的instance
*/
public static LazySingleton getInstance(){
if (instance == null){
instance = new LazySingleton();
}
return instance;
}
}
优缺点:
- 起到了 Lazy Loading 的效果,但是只能在单线程下使用。
- 如果在多线程下,一个线程进入了 if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式
懒汉式(线程安全,同步方法)
/**
* 懒汉式 2
* 线程安全 同步方法
*/
class LazySingleton {
private static LazySingleton instance;
public LazySingleton() {
}
/**
* 提供一个静态的共有方法,加入同步处理的代码,解决线程安全问题
* @return 返回创建的instance
*/
public static synchronized LazySingleton getInstance(){
if (instance == null){
instance = new LazySingleton();
}
return instance;
}
}
优缺点
- 解决了线程安全问题
- 效率太低了,每个线程在想获得类的实例时候,执行 getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接 return 就行了。方法进行同步效率太低
同步代码块(不能使用)
多个线程还是会进入if判断中,进行等待,实例化好对象后,下一个线程依旧会实例。
public static LazySingleton getInstance(){
if (instance == null){
synchronized(LazySingleton.class){
instance = new LazySingleton()
}
}
return instance;
}
双重判断(推荐使用)
class LazySingleton {
private static volatile LazySingleton instance;
public LazySingleton() {
}
/**
* 提供一个静态的共有方法,加入双重检查代码,解决线程安全问题,同时解决懒加载问题,又解决了效率问题
* @return 返回创建的instance
*/
public static synchronized LazySingleton getInstance() {
// 第一次判断
if (instance == null) {
synchronized (LazySingleton.class) {
// 第二次判断
if (instance == null) {
instance = new LazySingleton();
}
}
}
return instance;
}
}
静态内部类
/**
* 静态内部类
*/
class StaticSingleton {
/**
* 构造器私有化
*/
private StaticSingleton() {
}
/**
* 写一个静态内部类, 该类中有一个静态属性 StaticSingleton
*/
private static class StaticSingletonInstance{
private static final StaticSingleton INSTANCE = new StaticSingleton();
};
/**
* 提供一个静态的共有方法,直接返回 StaticSingletonInstance.INSTANCE
* @return 返回创建的instance
*/
public static synchronized StaticSingleton getInstance() {
return StaticSingletonInstance.INSTANCE;
}
}
优缺点:
- 这种方式采用了类装载的机制来保证初始化实例时只有一个线程。
- 静态内部类方式在 Singleton 类被装载时并不会立即实例化,而是在需要实例化时,调用 getInstance 方法,才会装载 SingletonInstance 类,从而完成 Singleton 的实例化。
- 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM 帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
- 优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高
枚举
public class SingletonTest07 {
public static void main(String[] args) {
EnumSingleton instance = EnumSingleton.INSTANCE;
EnumSingleton instance2 = EnumSingleton.INSTANCE;
System.out.println(instance == instance2);
}
}
enum EnumSingleton {
// 属性
INSTANCE;
public void sayOk() {
System.out.println("ok~~");
}
}
优缺点
- 这借助 JDK1.5 中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
工厂模式
简单工厂模式
又名:静态工厂模式
模式介绍
-
简单工厂模式是属于创建型模式,是工厂模式的一种。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式
-
简单工厂模式:定义了一个创建对象的类,由这个类来封装实例化对象的行为(代码)
-
在软件开发中,当我们会用到大量的创建某种、某类或者某批对象时,就会使用到工厂模式.
实例:订购pizza
图解:
// pizza 抽象类
public abstract class Pizza {
// pizza 名字
protected String name;
/** 抽象方法 prepare
* 准备原材料,不同的pizza 原材料不一样
*/
public abstract void prepare()
// 烘烤
public void bake() {
System.out.println(name + " baking ... ");
}
// 切割
public void cut() {
System.out.println(name + " cutting ... ");
}
// 打包
public void box() {
System.out.println(name + " boxing ... ");
}
public void setName(String name) {
this.name = name;
}
}
// 希腊pizza
public class GreekPizza extends Pizza{
@Override
public void prepare() {
System.out.println(" 给希腊pizza准备原材料 .... ");
}
}
// 奶酪pizza
public class CheesePizza extends Pizza{
@Override
public void prepare() {
System.out.println(" 给制作奶酪pizza 准备原材料 .... ");
}
}
// 制作pizza
public class OrderPizza {
/**
* 定义一个简单工厂对象
*/
SimpleFactory simpleFactory=null;
Pizza pizza = null;
public OrderPizza(SimpleFactory simpleFactory) {
setSimpleFactory(simpleFactory);
}
public void setSimpleFactory(SimpleFactory simpleFactory){
// 用户输入的种类
String orderType = "";
// 设置简单工厂对象
this.simpleFactory = simpleFactory;
do {
// 拿到用户想订购的类型
orderType = getType();
// 拿到制作好的pizza
pizza = this.simpleFactory.creatPizza(orderType);
if (pizza!=null){
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
}else {
System.out.println("订购pizza 失败 ... ");
break;
}
}while (true);
}
// 写一个方法,可以获取用户希望订购的pizza种类
private String getType() {
try {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
System.out.println("please input pizza type: ");
String str = bufferedReader.readLine();
return str;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
// 简单工厂
public class SimpleFactory {
/**
* 根据orderType 返回对应的Pizza对象
* @param orderType
* @return
*/
public Pizza creatPizza(String orderType) {
Pizza pizza = null;
String greek = "greek";
String cheese = "cheese";
System.out.println("使用简单的工厂模式");
if (greek.equals(orderType)) {
pizza = new GreekPizza();
pizza.setName("greek");
} else if (cheese.equals(orderType)) {
pizza = new GreekPizza();
pizza.setName("cheese");
}
return pizza;
}
}
public class PizzaStore {
public static void main(String[] args) {
// 使用简单工厂模式
new OrderPizza(new SimpleFactory());
System.out.println("退出程序 ... ");
}
}
问题来了! 新增pizza种类如何修改?
// 创建胡椒pizza 继承pizza类
public class PepperPizza extends Pizza{
@Override
public void prepare() {
System.out.println("给胡椒pizza准备原材料 ... ");
}
}
工厂方法模式
有新的需求,北京奶酪pizza,伦敦胡椒pizza,如何实现?
模式介绍
- 工厂方法模式设计方案:将披萨项目的实例化功能抽象成抽象方法,在不同的口味点餐子类中具体实现。
- 工厂方法模式:定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例化推迟到子类。
图解
代码实现
// pizza 抽象类
public abstract class Pizza {
// pizza 名字
protected String name;
/** 抽象方法 prepare
* 准备原材料,不同的pizza 原材料不一样
*/
public abstract void prepare()
// 烘烤
public void bake() {
System.out.println(name + " baking ... ");
}
// 切割
public void cut() {
System.out.println(name + " cutting ... ");
}
// 打包
public void box() {
System.out.println(name + " boxing ... ");
}
public void setName(String name) {
this.name = name;
}
}
// 伦敦奶酪pizza
public class LDPepperPizza extends Pizza{
@Override
public void prepare() {
setName("伦敦胡椒pizza");
System.out.println("伦敦胡椒pizza 准备材料中");
}
}
// 伦敦奶酪pizza
public class LDCheesePizza extends Pizza {
@Override
public void prepare() {
setName("伦敦奶酪pizza");
System.out.println("伦敦奶酪pizza 准备材料中");
}
}
// 北京胡椒pizza
public class BJPepperPizza extends Pizza{
@Override
public void prepare() {
setName("北京胡椒pizza");
System.out.println("北京的胡椒pizza 准备材料 ... ");
}
}
// 北京奶酪pizza
public class BJCheesePizza extends Pizza{
@Override
public void prepare() {
setName("北京奶酪pizza");
System.out.println("北京的奶酪pizza 准备材料 ... ");
}
}
// 选择pizza
public abstract class OrderPizza {
/**
* 定义一个抽象方法 createPizza,让各个工厂子类自己实现
* @param orderType
* @return
*/
abstract Pizza createPizza(String orderType);
/**
* 构造器
*/
public OrderPizza() {
Pizza pizza = null;
// 订购Pizza的类型
String orderType = null;
do {
orderType = getType();
// 抽象方法 由工厂子类完成
pizza = createPizza(orderType);
// 输出pizza过程
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
} while (true);
}
/**
* 写一个方法,可以获取用户希望订购的pizza种类
*/
private String getType() {
try {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
System.out.println("please input pizza type: ");
String str = bufferedReader.readLine();
return str;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
// 伦敦pizza选择
public class LDOrderPizza extends OrderPizza{
@Override
Pizza createPizza(String orderType) {
Pizza pizza = null;
String pepper = "pepper";
String cheese = "cheese";
if (cheese.equals(orderType)){
pizza = new LDCheesePizza();
}else if(pepper.equals(orderType)){
pizza = new LDPepperPizza();
}
return pizza;
}
}
// 北京pizza选择
public class BJOrderPizza extends OrderPizza{
@Override
Pizza createPizza(String orderType) {
Pizza pizza = null;
String pepper = "pepper";
String cheese = "cheese";
if (cheese.equals(orderType)){
pizza = new BJCheesePizza();
}else if(pepper.equals(orderType)){
pizza = new BJPepperPizza();
}
return pizza;
}
}
public class PizzaStore {
public static void main(String[] args) {
BJOrderPizza bjOrderPizza = new BJOrderPizza();
}
}
抽象工厂模式
基本介绍
- 抽象工厂模式:定义了一个 interface 用于创建相关或有依赖关系的对象簇,而无需指明具体的类
- 抽象工厂模式可以将简单工厂模式和工厂方法模式进行整合。
- 从设计层面看,抽象工厂模式就是对简单工厂模式的改进(或者称为进一步的抽象)。
- 将工厂抽象成两层,AbsFactory(抽象工厂) 和 具体实现的工厂子类。程序员可以根据创建对象类型使用对应的工厂子类。这样将单个的简单工厂类变成了工厂簇,更利于代码的维护和扩展。
图解
代码实现:
这几个类和上面的一样
public interface AbsFactory {
// 抽象方法: 让工厂子类来具体实现
public Pizza createPizza(String orderType);
}
public class BJFactory implements AbsFactory{
// 北京工厂 根据type实现制作pizza
@Override
public Pizza createPizza(String orderType) {
System.out.println("创建北京工厂 使用抽象模式");
Pizza pizza = null;
String cheese = "cheese";
String pepper = "pepper";
if (cheese.equals(orderType)){
pizza = new BJCheesePizza();
}else if (pepper.equals(orderType)){
pizza = new BJPepperPizza();
}
return pizza;
}
}
public class LDFactory implements AbsFactory{
// 伦敦工厂 根据type实现制作pizza
@Override
public Pizza createPizza(String orderType) {
System.out.println("创建伦敦工厂 使用抽象模式");
Pizza pizza = null;
String cheese = "cheese";
String pepper = "pepper";
if (cheese.equals(orderType)){
pizza = new LDCheesePizza();
}else if (pepper.equals(orderType)){
pizza = new LDPepperPizza();
}
return pizza;
}
}
public class OrderPizza {
AbsFactory factory;
// 构造器 实例化工厂的时候 设置工厂的种类
public OrderPizza(AbsFactory factory) {
setAbsFactory(factory);
}
// 实例化的时候 根据传入的工厂,去创建对应的pizza
// 具体pizza的type 参考getType()方法
private void setAbsFactory(AbsFactory factory) {
Pizza pizza = null;
String orderType = "";
this.factory = factory;
do {
orderType = getType();
// factory 可能是北京的工厂子类 也可能是伦敦的工厂子类
pizza = factory.createPizza(orderType);
if (pizza != null){
// 输出pizza过程
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
}else {
System.out.println("订购失败 ... ");
break;
}
} while (true);
}
// 写一个方法,可以获取用户希望订购的pizza种类
private String getType() {
try {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
System.out.println("please input pizza type: ");
String str = bufferedReader.readLine();
return str;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
public class PizzaStore {
public static void main(String[] args) {
new OrderPizza(new BJFactory());
}
}
使用场景
子类特别多的话使用抽象工厂,子类很少的话使用工厂方法。
代理模式
代理模式的基本介绍
-
代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
-
被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象
-
代理模式有不同的形式, 主要有三种 静态代理、动态代理(JDK 代理、接口代理)和 Cglib 代理 (可以在内存动态的创建对象,而不需要实现接口, 他是属于动态代理的范畴) 。
静态代理
- 代理类和被代理类必须实现相同接口或者父类
- 代理类中需要定义成员变量 来接受被代理者
实现步骤
代理模式:
1、创建接口
2、创建被代理对象 实现接口
3、创建代理对象 实现接口,创建被代理对象的引用 在对应的方法中 调用被代理对象的方法 并进行扩展
4、实例化被代理对象 和 代理对象
5、代理对象和被代理对象关联
6、调用代理对象的方法
代理对象:
方法实现
首先先实现被代理对象的方法(创建引用)
对方法进行扩展
// 接口
public interface Person {
void show();
void eat(String food);
}
// 被代理类
public class Student implements Person {
String name;
String sclass; // 班级
@Override
public void show() {
System.out.println("我是" + sclass + "班的学生: " + name);
}
@Override
public void eat(String food) {
System.out.println("我是:" + name + "我爱吃" + food);
}
public Student(String name, String sclass) {
this.name = name;
this.sclass = sclass;
}
}
// 被代理类
public class StudentProxy implements Person{
// 定义一个成员变量 来记录被代理对象
Person person;
public StudentProxy(Person person) {
this.person = person;
}
@Override
public void show() {
person.show();
System.out.println("再来首歌");
}
@Override
public void eat(String food) {
person.eat(food);
System.out.println("注意: 粒粒皆辛苦");
}
}
public class TestProxy {
public static void main(String[] args) {
// 先有代理对象
Student student = new Student("jsu", "信息");
// 创建代理对象
// 代理对象和被代理对象关联
StudentProxy studentProxy = new StudentProxy(student);
// 使用代理对象即可
studentProxy.show();
studentProxy.eat("芒果");
}
}
优缺点
缺点:
-
代理类和被代理类必须实现同一个接口 局限性比较大 而且代理类很多
-
代理类只能对指定接口类型的实现类进行控制 而且只能扩展接口中定义的方法
-
一旦接口增加方法,目标对象与代理对象都要维护
优点:
在不修改目标对象的功能前提下, 能通过代理对象对目标功能扩展
动态代理
基本介绍
- 代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理
- 代理对象的生成,是利用 JDK 的 API,动态的在内存中构建代理对象
- 动态代理也叫做:JDK 代理、接口代理
实现步骤
-
1 创建一个代理工厂类 实现接口InvocationHandler
-
2 定义成员变量记录 被代理对象
-
3 通过构造方法的参数列表 关联被代理对象
-
4 实现invoke方法 对目标对象的方法进行控制
-
5 定义一个方法:getInstance 来创建动态创建代理对象
-
6 定义被代理类:必须实现接口
-
7 创建被代理对象
-
8 创建代理工厂对象 并关联被代理对象
-
9 调用工厂对象的getProxyInstance方法来动态获取代理对象
// 接口 public interface IActive { void show(); void work(); }
public class Worker implements IActive { String name; Integer age; public Worker(String name, Integer age) { this.name = name; this.age = age; } @Override public void show(){ System.out.println("我是一名x学生:"+name+"今年"+age+"岁"); } @Override public void work(){ System.out.println(name+"学生,正在学习!"); } }
// 动态代理工厂类 public class ProxyFactory implements InvocationHandler { // 定义成员变量记录 被代理对象 private Object object; // 通过构造方法的参数列表 关联被代理对象 public ProxyFactory(Object object) { this.object = object; } // 实现invoke方法 对目标对象的方法进行控制 代理对象的方法每次调用的时候 就会执行invoke /* Object invoke(Object proxy, Method method, Object[] args) proxy : 代理对象 method : 被代理对象的方法 args : 方法的实参 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("目标对象方法之前的 ---- 预处理的代码"); // 调用目标对象的方法 Object invoke = method.invoke(object, args); System.out.println("目标对象方法之后的 ---- 预处理的代码"); return invoke; } // 定义一个方法: getProxyInstance // 此方法的目的 就是动态生成代理对象 public Object getProxyInstance() { /* Proxy.newProxyInstance(loader, interfaces, h); loader : 代理对象的类加载器 interfaces : 代理对象要实现的接口 -- 被代理对象实现的接口 h : InvocationHandler的实现对象 */ Object proxy = Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), this); return proxy; } }
// 测试类 public static void main(String[] args) { // 创建被代理对象 Worker su = new Worker("su", 28); // 创建工厂对象 ProxyFactory proxyFactory = new ProxyFactory(su); // 调用工厂对象的getProxyInstance方法 IActive proxyInstance = (IActive) proxyFactory.getProxyInstance(); proxyInstance.show(); proxyInstance.work(); }
Cglib
优势
CGLIB相比于JDK动态代理更加强大,JDK动态代理虽然简单易用,但是其有一个致命缺陷是,只能对接口进行代理。如果要代理的类为一个普通类、没有接口,那么Java动态代理就没法使用了
导包
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
代码实现
public class ProxyFactory implements MethodInterceptor {
// 维护一个目标对象
private Object target;
// 构造器 传入一个被代理的对象
public ProxyFactory(Object target) {
this.target = target;
}
// 重写 intercept 方法,会调用目标对象的方法
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("Cglib 代理模式 开始");
Object invoke = method.invoke(target, objects);
System.out.println("Cglib 代理模式 结束");
return invoke;
}
//返回一个代理对象: 是target 对象的代理对象
public Object getProxyInstance() {
//1. 创建一个工具类
Enhancer enhancer = new Enhancer();
//2. 设置父类
enhancer.setSuperclass(target.getClass());
//3. 设置回调函数
enhancer.setCallback(this);
//4. 创建子类对象,即代理对象
return enhancer.create();
}
}
public class Teacher {
public String teach() {
System.out.println(" 老师授课中 , 我是 cglib 代理,不需要实现接口 ");
return "hello";
}
}
// 测试
public class CglibTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
//创建目标对象
Teacher target = new Teacher();
//获取到代理对象,并且将目标对象传递给代理对象
Teacher proxyInstance = (Teacher) new ProxyFactory(target).getProxyInstance();
//执行代理对象的方法,触发 intercept 方法,从而实现 对目标对象的调用
String res = proxyInstance.teach();
System.out.println("res=" + res);
}
}
原型模式
基本介绍
- 原型模式(Prototype 模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
- 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节
- 工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即 对象**.clone**()
实例:克隆羊
传统方式
图解:
代码实现
public class Sheep {
private String name;
private Integer age;
private String color;
public Sheep(String name, Integer age, String color) {
this.name = name;
this.age = age;
this.color = color;
}
// 自己生成get set toString方法
}
public class Client {
public static void main(String[] args) {
// 传统的方法
Sheep sheep = new Sheep("tom", 1, "白色");
// 克隆羊
Sheep sheep1 = new Sheep(sheep.getName(), sheep.getAge(),sheep.getColor());
Sheep sheep2 = new Sheep(sheep.getName(), sheep.getAge(),sheep.getColor());
Sheep sheep3 = new Sheep(sheep.getName(), sheep.getAge(),sheep.getColor());
Sheep sheep4 = new Sheep(sheep.getName(), sheep.getAge(),sheep.getColor());
System.out.println(sheep);
System.out.println(sheep1);
System.out.println(sheep2);
System.out.println(sheep3);
System.out.println(sheep4);
}
}
优缺点:
- 优点是比较好理解,简单易操作。
- 在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,效率较低
- 总是需要重新初始化对象,而不是动态地获得对象运行时的状态, 不够灵活
浅拷贝
基本介绍
-
对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。
-
对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值
-
前面我们克隆羊就是浅拷贝
-
浅拷贝是使用默认的 clone()方法来实现
sheep = (Sheep) super.clone();
图解:
代码实现
public class Sheep implements Cloneable{
private String name;
private Integer age;
private String color;
// 克隆该实例 使用默认的clone方法完成
@Override
protected Object clone() {
Sheep sheep = null;
try{
sheep = (Sheep)super.clone();
}catch (CloneNotSupportedException e){
e.printStackTrace();
}
return sheep;
}
}
public class Client {
public static void main(String[] args) {
// 传统的方法
Sheep sheep = new Sheep("tom", 1, "白色");
// 克隆羊
Sheep clone1 = (Sheep) sheep.clone();
Sheep clone2 = (Sheep) sheep.clone()
}
}
深拷贝
基本介绍
- 复制对象的所有基本数据类型的成员变量值
- 为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象**(包括对象的引用类型)**进行拷贝
- 深拷贝实现方式 1:重写 clone 方法来实现深拷贝
- 深拷贝实现方式 2:通过对象序列化实现深拷贝(推荐)
代码实现
public class DeepCloneableTarget implements Serializable, Cloneable {
private static final long serialVersionUID = 1L;
private String cloneName;
private String cloneClass;
// 构造器
public DeepCloneableTarget(String cloneName, String cloneClass) {
this.cloneName = cloneName;
this.cloneClass = cloneClass;
}
// 因为该类的属性,都是 String , 因此我们这里使用默认的 clone 完成即可
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class DeepProtoType implements Serializable, Cloneable {
public String name;
// 引用类型
public DeepCloneableTarget deepCloneableTarget;
public DeepProtoType() {
super();
}
// 深拷贝 - 方式 1 使用 clone 方法
@Override
protected Object clone() throws CloneNotSupportedException {
Object deep = null;
//这里完成对基本数据类型(属性)和 String 的克隆
deep = super.clone();
//对引用类型的属性,进行单独处理
DeepProtoType deepProtoType = (DeepProtoType) deep;
deepProtoType.deepCloneableTarget = (DeepCloneableTarget) deepCloneableTarget.clone();
return deepProtoType;
}
// 深拷贝 - 方式 2 通过对象的序列化实现 (推荐)
public Object deepClone() {
//创建流对象
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
try {
//序列化
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
//当前这个对象以对象流的方式输出
oos.writeObject(this);
//反序列化
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
DeepProtoType copyObj = (DeepProtoType) ois.readObject();
return copyObj;
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
//关闭流
try {
bos.close();
oos.close();
bis.close();
ois.close();
} catch (Exception e2) {
System.out.println(e2.getMessage());
}
}
}
}
public class Client {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
DeepProtoType p = new DeepProtoType(); p.name = "宋江";
p.deepCloneableTarget = new DeepCloneableTarget("大牛", "小牛");
//方式 1 完成深拷贝
// DeepProtoType p2 = (DeepProtoType) p.clone();
//
// System.out.println("p.name=" + p.name + "p.deepCloneableTarget=" + p.deepCloneableTarget.hashCode());
// System.out.println("p2.name=" + p.name + "p2.deepCloneableTarget=" + p2.deepCloneableTarget.hashCode());
//方式 2 完成深拷贝
DeepProtoType p2 = (DeepProtoType) p.deepClone();
System.out.println("p.name=" + p.name + "p.deepCloneableTarget=" + p.deepCloneableTarget.hashCode());
System.out.println("p2.name=" + p.name + "p2.deepCloneableTarget=" + p2.deepCloneableTarget.hashCode());
}
}
建造者模式
基本介绍
- 建造者模式(Builder Pattern) 又叫生成器模式,是一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。
- 建造者模式 是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们, 用户不需要知道内部的具体构建细节。
四个角色
- Product(产品角色): 一个具体的产品对象。
- Builder(抽象建造者): 创建一个 Product 对象的各个部件指定的 接口**/**抽象类。
- ConcreteBuilder(具体建造者): 实现接口,构建和装配各个部件。
- Director(指挥者): 构建一个使用 Builder 接口的对象。它主要是用于创建一个复杂的对象。它主要有两个作用,一是:隔离了客户与对象的生产过程,二是:负责控制产品对象的生产过程。
图解
未使用建造者模式
使用建造者模式之后
代码实现
// Product 产品角色
public class House {
private String basic;
private String wall;
private String roofed;
// 生成getter setter
}
// 抽象建造者
public abstract class AbstractHouseBuilder {
protected House house = new House();
// 打地基
public abstract void buildBasic();
// 砌墙
public abstract void buildWalls();
//封顶
public abstract void roofed();
// 建造房子
//房子建造好, 将产品(房子)返回
public House buildHouse(){
return house;
}
}
//具体建造者
public class CommonHouse extends AbstractHouseBuilder {
@Override
public void buildBasic() {
System.out.println(" 普通房子打地基5米 ... ");
}
@Override
public void buildWalls() {
System.out.println(" 普通房子砌墙10cm ... ");
}
@Override
public void roofed() {
System.out.println(" 普通房子封顶茅草 ... ");
}
}
public class HighHouse extends AbstractHouseBuilder{
@Override
public void buildBasic() {
System.out.println(" 高楼房子打地基100米 ... ");
}
@Override
public void buildWalls() {
System.out.println(" 高楼房子砌墙20cm ... ");
}
@Override
public void roofed() {
System.out.println(" 高楼房子封顶玻璃顶 ... ");
}
}
// 指挥者 动态指定制作流程 返回产品
public class HouseDirector {
AbstractHouseBuilder houseBuilder = null;
// 构造器传入houseBuilder
public HouseDirector(AbstractHouseBuilder houseBuilder) {
this.houseBuilder = houseBuilder;
}
// 通过setter 传入houseBuilder
public void setHouseBuilder(AbstractHouseBuilder houseBuilder) {
this.houseBuilder = houseBuilder;
}
// 如何处理建造房子的流程,交给指挥者
public House constructHouse(){
houseBuilder.buildBasic();
houseBuilder.buildWalls();
houseBuilder.roofed();
return houseBuilder.buildHouse();
}
}
// 测试
public class Client {
public static void main(String[] args) {
// 建造模式的 普通房子和高楼 并不是属性上的不同 而是建造的过程流程不同
// 盖普通房子
CommonHouse commonBuilder = new CommonHouse();
// 准备盖房子的指挥者
HouseDirector houseDirector = new HouseDirector(commonBuilder);
// 完成盖房子 返回房子
House house = houseDirector.constructHouse();
}
}'
8.1 建造者模式的注意事项和细节
-
客户端(使用程序)不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象
-
每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者, 用户使用不同的具体建造者即可得到不同的产品对象
-
可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰, 也更方便使用程序来控制创建过程
-
增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程,系统扩展方便,符合“开闭原则”
-
建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
-
如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大,因此在这种情况下,要考虑是否选择建造者模式.
抽象工厂模式 VS 建造者模式
抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式不需要关心构建过程,只关心什么产品由什么工厂生产即可。而建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品
适配器模式
基本介绍
- 适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)
- 适配器模式属于结构型模式
- 主要分为三类:类适配器模式、对象适配器模式、接口适配器模式
工作原理
- 适配器模式:将一个类的接口转换成另一种接口.让原本接口不兼容的类可以兼容
- 从用户的角度看不到被适配者,是解耦的
- 用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法
- 用户收到反馈结果,感觉只是和目标接口交互,如图
类适配器
应用实例说明
以生活中充电器的例子来讲解适配器,充电器本身相当于 Adapter,220V 交流电相当于 src (即被适配者),我们的目 dst(即 目标)是 5V 直流电
图解:
代码实现
// 被适配的类
public class Voltage220V {
public int output220V(){
int src = 220;
System.out.println("电压为: "+ src +"V");
return src;
}
}
// 适配接口
public interface IVoltage5V {
// 转换为5v
public int output5V();
}
// 适配器类
public class VoltageAdapter extends Voltage220V implements IVoltage5V {
@Override
public int output5V() {
// 获取到220v的电压
int src = output220V();
// 获取到需要的电压 220/44=5V
int dstV = src / 44;
return dstV;
}
}
// 目标
public class Phone {
// 充电
public void charging(IVoltage5V iVoltage5V){
if (iVoltage5V.output5V() == 5){
System.out.println("电压为5V, 开始充电");
}else {
System.out.println("电压不是5V,无法充电");
}
}
}
// 测试
public class Client {
public static void main(String[] args) {
System.out.println("类适配器模式");
Phone phone = new Phone();
phone.charging(new VoltageAdapter());
}
}
类适配器模式注意事项和细节
- Java 是单继承机制,所以类适配器需要继承 src 类这一点算是一个缺点, 因为这要求 dst 必须是接口,有一定局限性;
- src 类的方法在 Adapter 中都会暴露出来,也增加了使用的成本。
- 由于其继承了 src 类,所以它可以根据需求重写 src 类的方法,使得 Adapter 的灵活性增强了。
对象适配器
基本介绍
- 基本思路和类的适配器模式相同,只是将 Adapter 类作修改,不是继承 src 类,而是持有 src 类的实例,以解决兼容性的问题。 即:持有 src 类,实现 dst 类接口,完成 src->dst 的适配
- 根据“合成复用原则”,在系统中尽量使用关联关系(聚合)来替代继承关系。
- 对象适配器模式是适配器模式常用的一种
应用实例
以生活中充电器的例子来讲解适配器,充电器本身相当于 Adapter,220V 交流电相当于 src (即被适配者),我们的目 dst(即目标)是 5V 直流电,使用对象适配器模式完成。
图解
代码实现
// 与类适配器比较只有VoltageAdapter 不同
public class VoltageAdapter implements IVoltage5V {
// 关联关系中的- 聚合关系
private Voltage220V voltage220V;
//通过构造器,传入一个Voltage220V实例
public VoltageAdapter(Voltage220V voltage220V) {
this.voltage220V = voltage220V;
}
@Override
public int output5V() {
// 定义需要的电压 默认为0
int dstV = 0;
// 判断voltage220V对象是否存在
if (null != voltage220V) {
// 获取到220v的电压
int src = voltage220V.output220V();
System.out.println("使用对象适配器进行转换");
dstV = src / 44;
System.out.println("适配完成,输出的电压为=" + dstV);
}
return dstV;
}
}
public class Client {
public static void main(String[] args) {
System.out.println("对象适配器模式");
Phone phone = new Phone();
phone.charging(new VoltageAdapter(new Voltage220V()));
}
}
对象适配器模式注意事项和细节
- 对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。
根据合成复用原则,使用组合替代继承, 所以它解决了类适配器必须继承 src 的局限性问题,也不再要求 dst必须是接口。
- 使用成本更低,更灵活。
接口适配器
基本介绍
- 一些书籍称为:适配器模式(Default Adapter Pattern)或缺省适配器模式。
- 核心思路:当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求
- 适用于一个接口不想使用其所有的方法的情况。
图解:
代码实现
// 接口
public interface Interface1 {
void m1();
void m2();
void m3();
void m4();
}
public interface Interface2 {
void t1();
void t2();
void t3();
void t4();
}
public class AbstractAdapter implements Interface1 ,Interface2{
// 默认实现
@Override
public void m1() {
}
......
}
// 测试
public class Client {
public static void main(String[] args) {
AbstractAdapter abstractAdapter = new AbstractAdapter() {
// 只需要去覆盖我们需要使用的方法就好了
@Override
public void m1() {
System.out.println("m1 run .... ");
}
@Override
public void t2() {
System.out.println("t2 run ....");
}
};
abstractAdapter.m1();
abstractAdapter.t2();
}
}
适配器模式的注意事项和细节
- 三种命名方式,是根据 src 是以怎样的形式给到 Adapter(在 Adapter 里的形式)来命名的。
- 类适配器:以类给到,在 Adapter 里,就是将 src 当做类,继承
- 对象适配器:以对象给到,在 Adapter 里,将 src 作为一个对象,持有接口适配器:以接口给到,在 Adapter 里,将 src 作为一个接口,实现
- Adapter 模式最大的作用还是将原本不兼容的接口融合在一起工作。
- 实际开发中,实现起来不拘泥于我们讲解的三种经典形式