设计模式 Day04
文章总结自B站尚硅谷
1. 原型模式
指用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象。允许一个对象再创建另外一个对象可定制的对象,无需知道如何创建的细节;要发动创建的对象通过请求原型对象拷贝它们来实施创建。
缺点:每一个类都需要配备克隆方法。
案例:创建10个一模一样的🐏。
1.1 传统模式
/**
* @date 2020/8/21 9:11
* 传统模式下的 🐏 的属性类
*/
public class Sheep {
private int age;
private String name;
private String color;
public Sheep(int age, String name, String color) {
this.age = age;
this.name = name;
this.color = color;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "Sheep{" +
"age=" + age +
", name='" + name + '\'' +
", color='" + color + '\'' +
'}';
}
}
/**
* @date 2020/8/21 9:12
* 普通类型下 创建多个一样的对象
*/
public class Test {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
System.out.println(new Sheep(1, "tom", "white").toString());
}
}
}
传统模式就是直接使用new实例化;
优势:好理解,易操作。
缺点:在创建对象时,总是需要获取原始对象的属性,消耗资源;总是需要初始化对象,不能直接获取对象运行的状态,不够灵活。
1.2 原型模式
/**
* @date 2020/8/21 9:11
* 原型模式下的 🐏 的属性类
*/
public class Sheep implements Cloneable {
private int age;
private String name;
private String color;
public Sheep(int age, String name, String color) {
this.age = age;
this.name = name;
this.color = color;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "Sheep{" +
"age=" + age +
", name='" + name + '\'' +
", color='" + color + '\'' +
'}';
}
// 重写Cloneable接口的方法,用来克隆该实例
@Override
protected Object clone(){
Sheep sheep = null;
try {
sheep = (Sheep) super.clone();
}catch (Exception e){
e.printStackTrace();
}
return sheep;
}
}
/**
* @date 2020/8/21 9:12
* 原型模式下 创建多个一样的对象
*/
public class Test {
public static void main(String[] args) {
Sheep sheep = new Sheep(1,"tom","white");
System.out.println(sheep);
for (int i = 0; i < 10; i++) {
System.out.println(sheep.clone());
}
}
}
相较于普通的模式而言,原型模式使得创建新的对象更加灵活,如果对象中新增了属性,那么只需要再原对象上进行添加,其它的对象依然直接进行克隆就行。
1.3 在Spring中的使用
在Spirng中创建Bean时使用到了原型模式。
1.4 深拷贝
复制所有的基本数据类型,为引用数据类型申请存储空间,并复制每个引用数据类型成员变量所引用的对象;也就是说在进行深拷贝时要对整个对象进行拷贝。
重写clone
方法或者是通过对象序列化来实现。
重写clone
:
// 深拷贝--重写clone
@Override
protected Object clone() throws CloneNotSupportedException {
Object deep = null;
// 对基本数据类型克隆
deep = super.clone();
// 对引用数据类型进行单独处理
DeepProtoType deepProtoType = (DeepProtoType) deep;
// 调用引用数据类型的clone方法
deepProtoType.setDeepCloneTarget((DeepCloneTarget) deepCloneTarget.clone());
return deep;
}
对象序列化:
// 深拷贝--通过对象序列化
public Object deepClone() throws IOException {
DeepProtoType copy = new DeepProtoType();
// 字节数组输出流
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);
copy = (DeepProtoType) ois.readObject();
}catch (Exception e){
e.printStackTrace();
}finally {
// 关闭流
bos.close();
oos.close();
bis.close();
ois.close();
}
return copy;
}
方式2相较于方式1而言更好使用,如果添加新的引用对象类型,也不需要更改方法中的代码,一劳永逸!
1.5 浅拷贝
对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行传值,也就是将该属性复制一份给新的对象;
对于数据类型是引用数据类型的成员变量,浅拷贝会进行引用传递,将该成员变量的内存地址复制给新的对象。
通过clone
方法实现。
2. 构建者模式
又称生成器模式,可以将复杂对象的建造过程抽象出来,使这个抽象过程的不同实现方法可以构造出不同表现的对象。一步步的创建一个复杂对象,允许用户通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道具体构建细节。
案例:盖房子,有高低不同的房子类型,盖房子步骤分为打地基-砌墙-封顶。
2.1 传统方式
盖房子类:
/**
* @date 2020/8/21 14:09
*
*/
public abstract class AbstractHouse {
// 打地基
public abstract void buildBasic();
// 砌墙
public abstract void buildWalls();
// 封顶
public abstract void roofed();
public void build(){
buildBasic();
buildWalls();
roofed();
}
}
盖普通房子:
/**
* @date 2020/8/21 14:12
*/
public class CommonHouse extends AbstractHouse {
@Override
public void buildBasic() {
System.out.println("给普通房子打地基");
}
@Override
public void buildWalls() {
System.out.println("给普通房子砌墙");
}
@Override
public void roofed() {
System.out.println("给普通房子封顶");
}
}
盖高房子:
/**
* @date 2020/8/21 14:12
*/
public class HighBuilding extends AbstractHouse {
@Override
public void buildBasic() {
System.out.println("给高房子打地基");
}
@Override
public void buildWalls() {
System.out.println("给高房子砌墙");
}
@Override
public void roofed() {
System.out.println("给高房子封顶");
}
}
客户端:
/**
* @date 2020/8/21 14:22
*/
public class Client {
public static void main(String[] args) {
CommonHouse commonHouse = new CommonHouse();
commonHouse.build();
HighBuilding highBuilding = new HighBuilding();
highBuilding.build();
}
}
优点:好理解,易操作
缺点:结构过于简单,没有设计缓存层对象,程序的扩展和维护不好,耦合性太高。
2.2 建造者模式
dao:
/**
* @date 2020/8/21 14:40
*/
public class House {
private String basic;
private String wall;
private String roofed;
public String getBasic() {
return basic;
}
public void setBasic(String basic) {
this.basic = basic;
}
public String getWall() {
return wall;
}
public void setWall(String wall) {
this.wall = wall;
}
public String getRoofed() {
return roofed;
}
public void setRoofed(String roofed) {
this.roofed = roofed;
}
}
抽象类:提供方法让下方的不同的类别的房子进行调用。
/**
* @date 2020/8/21 14:41
*/
public abstract class HouseBuild {
protected House house = new House();
public abstract void buildBasic();
public abstract void buildWall();
public abstract void roofed();
public House build(){
buildBasic();
buildWall();
roofed();
return house;
}
}
房子类型类:继承抽象类,根据自己的情况去实现方法。
/**
* @date 2020/8/21 14:43
*/
public class CommonHouse extends HouseBuild {
@Override
public void buildBasic() {
System.out.println("给普通房子打地基");
}
@Override
public void buildWall() {
System.out.println("给普通房子砌墙");
}
@Override
public void roofed() {
System.out.println("给普通房子封顶");
}
}
领导者类:真正指导如何去盖房子的类
public class HouseDirector {
HouseBuild houseBuild = null;
// 方式1 构造器 传入 houseBuild
public HouseDirector(HouseBuild houseBuild) {
this.houseBuild = houseBuild;
}
public HouseDirector(){
}
// 方式2 setter 传入 houseBuild
public void setHouseBuild(HouseBuild houseBuild) {
this.houseBuild = houseBuild;
}
public House constructHouse(){
return houseBuild.build();
}
}
客户端:
/**
* @date 2020/8/21 14:49
*/
public class Client {
public static void main(String[] args) {
HouseDirector houseDirector = new HouseDirector(new HighBuilding());
houseDirector.constructHouse();
}
}
2.3 Spring中的使用
在Spring中的SpringBuilder
类使用了构建者模式。
2.4 注意事项和细节
客户端不必知道产品内部组成的细节,将产品本身与产品创建过程解耦;每一个具体构建者都独立,而与其它的具体构建者无关,用户使用不同的具体构建者即可得到不同的产品对象;可以更加精确的控制产品的创建过程;增加新的具体创建者无需修改原有的代码。
3. 适配器模式
将某类的接口转化成客户端期望的另一个接口表示,让原本不能在一起工作的两个类协同工作,主要目的是增强兼容性!
案例:手机充电,但是手机只能接收5v,通过适配器将220v转为5v
3.1 类适配器
需要被适配的类
/**
* @date 2020/8/21 15:18
*/
public class Voltage220V {
public int output220V(){
return 220;
}
}
适配(把被适配的类转为适配这个)的类
/**
* @date 2020/8/21 15:20
*/
public interface Voltage5V {
public int output5V();
}
适配器:
/**
* @date 2020/8/21 15:21
*/
public class VoltageAdapter extends Voltage220V implements Voltage5V {
@Override
public int output5V() {
int src = output220V();
int dst = src / 44;
return dst;
}
}
手机:
/**
* @date 2020/8/21 15:23
*/
public class Phone {
public void charging(Voltage5V v){
if (v.output5V() == 5){
System.out.println("电压5v,可以充电");
}else {
System.out.println("电压不是5v,不可用充电");
}
}
}
客户端:
/**
* @date 2020/8/21 15:25
*/
public class Client {
public static void main(String[] args) {
Phone phone = new Phone();
phone.charging(new VoltageAdapter());
}
}
缺点:Java单继承机制,必须要求被适配的类是类,而要适配为的类就必须为一个接口。被适配的类的方法在适配器中暴露出来。
优点:可以根据要求编写被适配器的方法,使得适配器的灵活性增强了。
3.2 对象适配器
与类适配器不同的是,不再继承被适配类,而是持有它的实例,用来解决兼容性问题。
在上方类适配器代码的基础上进行修改:
适配器:
/**
* @date 2020/8/21 15:21
*/
public class VoltageAdapter implements Voltage5V {
private Voltage220V v;
public VoltageAdapter(Voltage220V v) {
this.v = v;
}
@Override
public int output5V() {
int dist = 0;
if (v != null){
int src = v.output220V();
dist = src/44;
return dist;
}
return dist;
}
}
客户端:
/**
* @date 2020/8/21 15:25
*/
public class Client {
public static void main(String[] args) {
Phone phone = new Phone();
phone.charging(new VoltageAdapter(new Voltage220V()));
}
}
类与类之间的关系由泛化(继承)变为了聚合。所有就不再要求必须要继承被适配类,目标适配类也不再必须为接口。成本更低,更加灵活。
3.3 接口适配器
当不需要全部接口提供的方法时,可以设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现的空方法,该抽象的子类可以有选择的覆盖父类中某些方法来实现需求;适用于与一个接口不想使用其所有方法的情况。
接口:
/**
* @date 2020/8/21 15:49
*/
public interface IInterface {
public void m1();
public void m2();
public void m3();
public void m4();
}
抽象类:
/**
* @date 2020/8/21 15:50
*/
public class AbsAdapter implements IInterface {
@Override
public void m1() {
}
@Override
public void m2() {
}
@Override
public void m3() {
}
@Override
public void m4() {
}
}
客户端:
在调用时,使用匿名客户端去重写抽象类实现的接口中的方法。
/**
* @date 2020/8/21 15:50
*/
public class Client {
public static void main(String[] args) {
AbsAdapter absAdapter = new AbsAdapter() {
@Override
public void m1() {
System.out.println("m1 -- running");
}
};
absAdapter.m1();
}
}
3.4 SpringMVC
在SpingMVC框架的HandlerAdapter
中用到了适配器模式。
4. 桥接模式
将实现与抽象放在两个不同的类层次中,使两个层次可以独立改变;基于最喜小的设计原则,听过封装、聚合及继承为让不同的类承担不同的职责。
案例:手机有不同的品牌和不同的打开方式。
提供方法的接口:
/**
* @date 2020/8/21 16:46
*/
public interface Brand {
void open();
void close();
void call();
}
品牌:
/**
* @date 2020/8/21 16:47
*/
public class Apple implements Brand{
@Override
public void open() {
System.out.println("苹果手机开机");
}
@Override
public void close() {
System.out.println("苹果收件关机");
}
@Override
public void call() {
System.out.println("苹果手机打电话");
}
}
/**
* @date 2020/8/21 16:47
*/
public class HuaWei implements Brand{
@Override
public void open() {
System.out.println("华为手机开机");
}
@Override
public void close() {
System.out.println("华为收件关机");
}
@Override
public void call() {
System.out.println("华为手机打电话");
}
}
打开方式:
/**
* @date 2020/8/21 16:54
*/
public class FoldedPhone extends Phone{
public FoldedPhone(Brand brand) {
super(brand);
}
@Override
protected void open() {
super.open();
System.out.println("折叠样式的手机");
}
@Override
protected void close() {
super.close();
System.out.println("折叠样式的手机");
}
@Override
protected void call() {
super.call();
System.out.println("折叠样式的手机");
}
}
抽象类:
/**
* @date 2020/8/21 16:53
*/
public abstract class Phone {
private Brand brand;
public Phone(Brand brand) {
this.brand = brand;
}
protected void open(){
this.brand.open();
}
protected void close(){
this.brand.close();
}
protected void call(){
this.brand.call();
}
}
客户端:
/**
* @date 2020/8/21 16:58
*/
public class Client {
public static void main(String[] args) {
FoldedPhone foldedPhone = new FoldedPhone(new Apple());
foldedPhone.open();
foldedPhone.call();
foldedPhone.close();
}
}
这种方式,如果要新增一个新的手机品牌或者手机打开方式,只需要添加相应的类即可,不需要改变其它的代码。
这种方式在JDBC的Driver接口中有使用
优点:
- 实现了抽象和实现的分离,从而极大的提供了系统的灵活性,有助于系统的分层设计,从而产生更好的结构化体系;
- 代替了多层继承方案,减少了子类的个数,降低系统的管理和维护成本;
- 对于系统高层的部分,只需要知道抽象部分和实现部分的接口就可以,其它的部分由具体业务来完成;
缺点:增加了系统的理解和设计难度,由于聚合关联关系在抽象层,要求开发者针对抽象进行设计和编程;要求正确的识别出系统两个独立变化的维度,有一定的局限性。