系列文章目录
Java设计模式笔记——七大设计原则
文章目录
- 系列文章目录
- 一、简单工厂模式
- 1.概念
- 2.案例分析
- 3.使用简单工厂模式进行改进
- 4.总结
- 二、工厂方法模式
- 1.概念
- 2.案例分析
- 3.使用工厂方法模式进行改进
- 4.总结
- 三、抽象工厂模式
- 1.概念
- 2.案例分析
- 3.使用抽象工厂模式改进
- 4.总结
- 四、单例模式
- 1.概念
- 2.饿汉式单例
- 3.懒汉式单例(线程不安全)
- 4.懒汉式单例(线程安全,同步方法)
- 5.懒汉式单例(线程安全,同步代码块)
- 6.懒汉式单例(双重检查)
- 7.静态内部类单例
- 8.枚举
- 五、原型模式
- 1.概念
- 2.浅拷贝
- 3.深拷贝
- 六、建造者模式
- 1.概念
- 2.使用
- 总结
一、简单工厂模式
1.概念
工厂模式是最常用的一类创建型设计模式,通常我们所说的工厂模式是指工厂方法模式,它也是使用频率最高的工厂模式
简单工厂模式(Simple Factory Pattern):定义一个工厂类,它可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类。因为在简单工厂模式中用于创建实例的方法是静态(static)方法,因此简单工厂模式又被称为静态工厂方法(Static Factory Method)模式,它属于类创建型模式
。
简单工厂模式的要点在于:当你需要什么,只需要传入一个正确的参数,就可以获取你所需要的对象,而无须知道其创建细节。简单工厂模式结构比较简单,其核心是工厂类的设计,其结构如下所示:
- Product(抽象产品角色):它是工厂类所创建的所有对象的父类,封装了各种产品对象的公有方法,它的引入将提高系统的灵活性,使得在工厂类中只需定义一个通用的工厂方法,因为所有创建的具体产品对象都是其子类对象。
- ConcreteProduct(具体产品角色):它是简单工厂模式的创建目标,所有被创建的对象都充当这个角色的某个具体类的实例。每一个具体产品角色都继承了抽象产品角色,需要实现在抽象产品中声明的抽象方法。
- Factory(工厂角色):工厂角色即工厂类,它是简单工厂模式的核心,负责实现创建所有产品实例的内部逻辑;工厂类可以被外界直接调用,创建所需的产品对象;在工厂类中提供了静态的工厂方法factoryMethod(),它的返回类型为抽象产品类型Product。
2.案例分析
先在有一个需求是开发一套图表库,该图表库可以为应用系统提供各种不同外观的图表,例如柱状图、饼状图、折线图等。图表库设计人员希望为应用系统开发人员提供一套灵活易用的图表库,而且可以较为方便地对图表库进行扩展,以便能够在将来增加一些新类型的图表。如下是传统方案:
public class Chart {
private String type;
AbstractChart abstractChart;
public Chart(String type) {
this.type = type;
if (type.equalsIgnoreCase("bar")) {
//初始化柱状图
abstractChart = new BarChart();
} else if (type.equalsIgnoreCase("pie")) {
//初始化饼状图
abstractChart = new PieChart();
} else if (type.equalsIgnoreCase("line")) {
//初始化折线图
abstractChart = new LineChart();
}
}
public void display() {
if (type.equalsIgnoreCase("bar")) {
abstractChart.display();
} else if (type.equalsIgnoreCase("pie")) {
abstractChart.display();
} else if (type.equalsIgnoreCase("line")) {
abstractChart.display();
}
}
}
Chart类是一个“巨大的”类,在该类的设计中存在如下几个问题:
- 在Chart类中包含很多“if…else…”代码块,整个类的代码相当冗长,代码越长,阅读难度、维护难度和测试难度也越大;而且大量条件语句的存在还将影响系统的性能,程序在执行过程中需要做大量的条件判断。
- Chart类的职责过重,它负责初始化和显示所有的图表对象,将各种图表对象的初始化代码和显示代码集中在一个类中实现,违反了“单一职责原则”,不利于类的重用和维护;而且将大量的对象初始化代码都写在构造函数中将导致构造函数非常庞大,对象在创建时需要进行条件判断,降低了对象创建的效率。
- 当需要增加新类型的图表时,必须修改Chart类的源代码,违反了“开闭原则”。
- 客户端只能通过new关键字来直接创建Chart对象,Chart类与客户端类耦合度较高,对象的创建和使用无法分离。
- 客户端在创建Chart对象之前可能还需要进行大量初始化设置,例如设置柱状图的颜色、高度等,如果在Chart类的构造函数中没有提供一个默认设置,那就只能由客户端来完成初始设置,这些代码在每次创建Chart对象时都会出现,导致代码的重复。
3.使用简单工厂模式进行改进
public interface Chart {
void display();
}
public class BarChart implements Chart{
@Override
public void display() {
System.out.println("画柱状状图表...");
}
}
public class LineChart implements Chart{
@Override
public void display() {
System.out.println("画折线图表...");
}
}
public class PieChart implements Chart{
@Override
public void display() {
System.out.println("画饼状图表...");
}
}
public class ChartFactory {
public static Chart getChart(String type) {
Chart chart = null;
if (type.equalsIgnoreCase("bar")) {
chart = new BarChart();
System.out.println("初始化设置柱状图!");
} else if (type.equalsIgnoreCase("pie")) {
chart = new PieChart();
System.out.println("初始化设置饼状图!");
} else if (type.equalsIgnoreCase("line")) {
chart = new LineChart();
System.out.println("初始化设置折线图!");
}
return chart;
}
}
public class Client {
public static void main(String[] args) {
Chart chart = ChartFactory.getChart("pie");
chart.display();
}
}
4.总结
简单工厂模式的主要优点如下
:
- 工厂类包含必要的判断逻辑,可以决定在什么时候创建哪一个产品类的实例,客户端可以免除直接创建产品对象的职责,而仅仅“消费”产品,简单工厂模式实现了对象创建和使用的分离。
- 客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可,对于一些复杂的类名,通过简单工厂模式可以在一定程度减少使用者的记忆量。
简单工厂模式的主要缺点
:
- 由于工厂类集中了所有产品的创建逻辑,职责过重,一旦不能正常工作,整个系统都要受到影响。
- 使用简单工厂模式势必会增加系统中类的个数(引入了新的工厂类),增加了系统的复杂度和理解难度。
- 系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,在产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展和维护。
- 简单工厂模式由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构。
在以下情况下可以考虑使用简单工厂模式:
- 工厂类负责创建的对象比较少,由于创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂。
- 客户端只知道传入工厂类的参数,对于如何创建对象并不关心。
二、工厂方法模式
1.概念
简单工厂模式虽然简单,但存在一个很严重的问题。当系统中需要引入新产品时,由于静态工厂方法通过所传入参数的不同来创建不同的产品,这必定要修改工厂类的源代码,将违背“开闭原则”。
工厂方法模式(Factory Method Pattern):定义一个用于创建对象的接口,让子类决定将哪一个类实例化。工厂方法模式让一个类的实例化延迟到其子类。工厂方法模式又简称为工厂模式(Factory Pattern),又可称作虚拟构造器模式(Virtual Constructor Pattern)或多态工厂模式(Polymorphic Factory Pattern)。工厂方法模式是一种类创建型模式。
工厂方法模式提供一个抽象工厂接口来声明抽象工厂方法,而由其子类来具体实现工厂方法,创建具体的产品对象。工厂方法模式结构如下所示:
在工厂方法模式结构图中包含如下几个角色:
- Product(抽象产品):它是定义产品的接口,是工厂方法模式所创建对象的超类型,也就是产品对象的公共父类。
- ConcreteProduct(具体产品):它实现了抽象产品接口,某种类型的具体产品由专门的具体工厂创建,具体工厂和具体产品之间一一对应。
- Factory(抽象工厂):在抽象工厂类中,声明了工厂方法(Factory Method),用于返回一个产品。抽象工厂是工厂方法模式的核心,所有创建对象的工厂类都必须实现该接口。
- ConcreteFactory(具体工厂):它是抽象工厂类的子类,实现了抽象工厂中定义的工厂方法,并可由客户端调用,返回一个具体产品类的实例。
2.案例分析
开发一个系统运行日志记录器(Logger),该记录器可以通过多种途径保存系统的运行日志,如通过文件记录或数据库记录,用户可以通过修改配置文件灵活地更换日志记录方式。使用简单工厂模式对日志记录器进行了设计结构如下所示:
public interface LoggerSimple {
void wirteLog();
}
public class DataBaseLoggerSimple implements LoggerSimple{
@Override
public void wirteLog() {
System.out.println("数据库日志写入...");
}
}
public class FileLoggerSimple implements LoggerSimple{
@Override
public void wirteLog() {
System.out.println("写入文件日志...");
}
}
public class LoggerFactorySimple {
public static LoggerSimple createLogger(String type) {
if (type.equalsIgnoreCase("db")) {
LoggerSimple logger = new DataBaseLoggerSimple();
return logger;
} else if (type.equalsIgnoreCase("file")) {
LoggerSimple logger = new FileLoggerSimple();
return logger;
} else {
return null;
}
}
}
public class Client {
public static void main(String[] args) {
LoggerSimple loggerSimple = LoggerFactorySimple.createLogger("db");
loggerSimple.wirteLog();
}
}
虽然简单工厂模式实现了对象的创建和使用分离,但是仍然存在如下两个问题:
- 工厂类过于庞大,包含了大量的if…else…代码,导致维护和测试难度增大;
- 系统扩展不灵活,如果增加新类型的日志记录器,必须修改静态工厂方法的业务逻辑,违反了“开闭原则”。
3.使用工厂方法模式进行改进
Logger接口充当抽象产品,其子类FileLogger和DatabaseLogger充当具体产品,LoggerFactory接口充当抽象工厂,其子类FileLoggerFactory和DatabaseLoggerFactory充当具体工厂。
public interface Logger {
void wirteLog();
}
public interface LoggerFactory {
Logger createLogger();
}
public class FileLogger implements Logger {
@Override
public void wirteLog() {
System.out.println("写入文件日志...");
}
}
public class DataBaseLogger implements Logger {
@Override
public void wirteLog() {
System.out.println("数据库日志写入...");
}
}
public class FileLoggerFactory implements LoggerFactory{
@Override
public Logger createLogger() {
System.out.println("文件Logger创建...");
return new FileLogger();
}
}
public class DatabaseLoggerFactory implements LoggerFactory{
@Override
public Logger createLogger() {
System.out.println("数据库Logger创建...");
return new DataBaseLogger();
}
}
public class Client {
public static void main(String[] args) {
LoggerFactory factory = new FileLoggerFactory();
Logger logger = factory.createLogger();
logger.wirteLog();
factory = new DatabaseLoggerFactory();
logger = factory.createLogger();
logger.wirteLog();
}
}
4.总结
工厂方法模式是简单工厂模式的延伸,它继承了简单工厂模式的优点,同时还弥补了简单工厂模式的不足。工厂方法模式是使用频率最高的设计模式之一,是很多开源框架和API类库的核心模式。
主要优点
:
- 在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。
- 基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。它能够让工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。工厂方法模式之所以又被称为多态工厂模式,就正是因为所有的具体工厂类都具有同一抽象父类。
- 使用工厂方法模式的另一个优点是在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了,这样,系统的可扩展性也就变得非常好,完全符合“开闭原则”。
主要缺点
:
- 在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。
- 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。
适用场景:
- 客户端不知道它所需要的对象的类。在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建,可将具体工厂类的类名存储在配置文件或数据库中。
- 抽象工厂类通过其子类来指定创建哪个对象。在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。
三、抽象工厂模式
1.概念
抽象工厂模式(Abstract Factory Pattern):提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式又称为Kit模式,它是一种对象创建型模式。
抽象工厂模式为创建一组对象提供了一种解决方案。与工厂方法模式相比,抽象工厂模式中的具体工厂不只是创建一种产品,它负责创建一族产品。如一个电器工厂,它可以生产电视机、电冰箱、空调等多种电器,而不是只生产某一种电器。为了更好地理解抽象工厂模式,我们先引入两个概念:
- 产品等级结构:产品等级结构即产品的继承结构,如一个抽象类是电视机,其子类有海尔电视机、海信电视机、TCL电视机,则抽象电视机与具体品牌的电视机之间构成了一个产品等级结构,抽象电视机是父类,而具体品牌的电视机是其子类。
- 产品族:在抽象工厂模式中,产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品,如海尔电器工厂生产的海尔电视机、海尔电冰箱,海尔电视机位于电视机产品等级结构中,海尔电冰箱位于电冰箱产品等级结构中,海尔电视机、海尔电冰箱构成了一个产品族。
在抽象工厂模式中,每一个具体工厂都提供了多个工厂方法用于产生多种不同类型的产品,这些产品构成了一个产品族,抽象工厂模式结构如下所示:
- ConcreteFactory(具体工厂):它实现了在抽象工厂中声明的创建产品的方法,生成一组具体产品,这些产品构成了一个产品族,每一个产品都位于某个产品等级结构中。
- AbstractProduct(抽象产品):它为每种产品声明接口,在抽象产品中声明了产品所具有的
业务方法。
ConcreteProduct(具体产品):它定义具体工厂生产的具体产品对象,实现抽象产品接口中
声明的业务方法。
2.案例分析
现有一个海尔工厂与一个小米工厂,其中会生产冰箱、空调等产品,我们使用方法工厂模式实现如下:
存在问题:
- 当需要增加新的工厂时,虽然不要修改现有代码,但是需要增加大量类,针对每一个新增具体产品都需要增加一个具体工厂,类的个数成对增加,这无疑会导致系统越来越庞大,增加系统的维护成本和运行开销;
- 由于同一个公司的具体产品通常要一起生产,因此需要为每个产品都选择一个具体工厂,如果某个具体工厂选择失误将会导致产品生产混乱,虽然我们可以适当增加一些约束语句,但客户端代码和配置文件都较为复杂。
3.使用抽象工厂模式改进
public interface Refrigerator {
}
public interface Air {
}
public interface ElecFactory {
Refrigerator createRefrigerator();
Air createAir();
}
public class HaierAir implements Air{
public HaierAir() {
System.out.println("创建海尔空调...");
}
}
public class XiaomiAir implements Air{
public XiaomiAir() {
System.out.println("创建小米空调...");
}
}
public class HaierRefrigerator implements Refrigerator {
public HaierRefrigerator() {
System.out.println("创建海尔冰箱...");
}
}
public class XiaomiRefrigerator implements Refrigerator{
public XiaomiRefrigerator() {
System.out.println("创建小米冰箱...");
}
}
public class HaierFactory implements ElecFactory{
@Override
public Refrigerator createRefrigerator() {
return new HaierRefrigerator();
}
@Override
public Air createAir() {
return new HaierAir();
}
}
public class XiaomiFactory implements ElecFactory{
@Override
public Refrigerator createRefrigerator() {
return new XiaomiRefrigerator();
}
@Override
public Air createAir() {
return new XiaomiAir();
}
}
public class Client {
public static void main(String[] args) {
ElecFactory elecFactory = new HaierFactory();
elecFactory.createAir();
elecFactory.createRefrigerator();
elecFactory = new XiaomiFactory();
elecFactory.createAir();
elecFactory.createRefrigerator();
}
}
4.总结
抽象工厂模式是工厂方法模式的进一步延伸,由于它提供了功能更为强大的工厂类并且具备较好的可扩展性,在软件开发中得以广泛应用,尤其是在一些框架和API类库的设计中,例如在Java语言的AWT(抽象窗口工具包)中就使用了抽象工厂模式,它使用抽象工厂模式来实现在不同的操作系统中应用程序呈现与所在操作系统一致的外观界面。
主要优点:
- 抽象工厂模式隔离了具体类的生成,使得客户并不需要知道什么被创建。由于这种隔离,更换一个具体工厂就变得相对容易,所有的具体工厂都实现了抽象工厂中定义的那些公共接口,因此只需改变具体工厂的实例,就可以在某种程度上改变整个软件系统的行为。
- 当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。
- 增加新的产品族很方便,无须修改已有系统,符合“开闭原则”。
主要缺点:
增加新的产品等级结构麻烦,需要对原有系统进行较大的修改,甚至需要修改抽象层代码,这显然会带来较大的不便,违背了“开闭原则”。
适用场景:
- 一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有类型的工厂模式都是很重要的,用户无须关心对象的创建过程,将对象的创建和使用解耦。
- 系统中有多于一个的产品族,而每次只使用其中某一产品族。可以通过配置文件等方式来使得用户可以动态改变产品族,也可以很方便地增加新的产品族。
- 属于同一个产品族的产品将在一起使用,这一约束必须在系统的设计中体现出来。同一个产品族中的产品可以是没有任何关系的对象,但是它们都具有一些共同的约束,如同一操作系统下的按钮和文本框,按钮与文本框之间没有直接关系,但它们都是属于某一操作系统的,此时具有一个共同的约束条件:操作系统的类型。
- 产品等级结构稳定,设计完成之后,不会向系统中增加新的产品等级结构或者删除已有的产品等级结构。
四、单例模式
1.概念
单例模式定义如下: 单例模式(Singleton Pattern):确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。单例模式是一种对象创建型模式。
单例模式有三个要点:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。
创建要求:构造器私有化、类的内部创建对象、向外暴露一个静态的公共方法
2.饿汉式单例
- 静态常量实现
public class SingleHungry {
private final static SingleHungry SINGLE_HUNGRY = new SingleHungry();
private SingleHungry() {
}
public static SingleHungry getInstance() {
return SINGLE_HUNGRY;
}
}
- 静态代码块实现
public class SingleHungry1 {
private static SingleHungry1 singleHungry1;
static {
singleHungry1 = new SingleHungry1();
}
private SingleHungry1() {
}
public static SingleHungry1 getInstance() {
return singleHungry1;
}
}
饿汉式优缺点:
- 优点:这种写法简单,就是在类装载的时候完成实例化,避免了线程问题
- 缺点:在类的装载的时候就完成实例化,没有达到懒加载的效果,如果从始至终都没有使用到这个类,就会造成内存浪费。
3.懒汉式单例(线程不安全)
public class SingleLazy {
private static SingleLazy singleLazy;
private SingleLazy() {
}
public static SingleLazy getInstance() {
if(singleLazy == null){
singleLazy = new SingleLazy();
}
return singleLazy;
}
}
起到了懒加载的效果,但是只能在单线程下使用,在多线程下,一个线程进入了if 判断语句块,还未来得及往下执行,另外一个线程也通过了这个判断语句,就会产生多个实例,在实例开发中不要使用这种方式。
4.懒汉式单例(线程安全,同步方法)
public class SingleLazy1 {
private static SingleLazy1 singleLazy;
private SingleLazy1() {
}
public static synchronized SingleLazy1 getInstance() {
if(singleLazy == null){
singleLazy = new SingleLazy1();
}
return singleLazy;
}
}
解决了线程安全问题,每个线程在想获得类的实例时候,执行getInstance()方法都需要同步。方法进行同步效率太低,实际开发中不推荐使用。
5.懒汉式单例(线程安全,同步代码块)
public class SingleLazy2 {
private static SingleLazy2 singleLazy;
private SingleLazy2() {
}
public static SingleLazy2 getInstance() {
if(singleLazy == null){
synchronized (SingleLazy2.class){
singleLazy = new SingleLazy2();
}
}
return singleLazy;
}
}
和同步方法一样,不推荐使用
6.懒汉式单例(双重检查)
public class SingleLazy3 {
private static SingleLazy3 singleLazy;
private SingleLazy3() {
}
public static SingleLazy3 getInstance() {
if(singleLazy == null){
synchronized (SingleLazy3.class){
if(singleLazy == null){
singleLazy = new SingleLazy3();
}
}
}
return singleLazy;
}
}
双重检查是多线程开发中经常使用到的,我们进行了两次判断,这样就可以保证线程安全了,这样实例化代码只用执行一次,后面线程在访问时,直接return实例化对象。总的来说:线程安全、延迟加载、效率较高,推荐使用。
7.静态内部类单例
public class Single {
private Single() {
}
private static class SingleInstance{
private final static Single single = new Single();
}
public static Single getInstance(){
return SingleInstance.single;
}
}
说明:
- 这中方式采用了类装载的机制来保证初始化实例时只有一个线程。
- 静态内部类方式在Single类被装载时并不会立即实例化,而是在调用getInstance()方法时,才会装载SingleInstance,从而完成Single类的实例化。
避免了线程不安全,利用内部类特点实现延迟加载,效率高,推荐使用。
8.枚举
public enum SingleEnum {
/**
* 属性
*/
INSTANCE;
public void hello(){
System.out.println("hello word");
}
}
public class Client {
public static void main(String[] args) {
SingleEnum singleEnum = SingleEnum.INSTANCE;
SingleEnum singleEnum1 = SingleEnum.INSTANCE;
System.out.println(singleEnum1 == singleEnum);
singleEnum.hello();
}
}
借助jdk1.5中添加枚举的方式来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
五、原型模式
1.概念
原型模式的定义如下: 原型模式(Prototype Pattern):使用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。原型模式是一种对象创建型模式。
原型模式的工作原理很简单:将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝自己来实现创建过程。由于在软件系统中我们经常会遇到需要创建多个相同或者相似对象的情况,因此原型模式在真实开发中的使用频率还是非常高的。原型模式是一种“另类”的创建型模式,创建克隆对象的工厂就是原型类自身,工厂方法由克隆方法来实现。
- Prototype(抽象原型类):它是声明克隆方法的接口,是所有具体原型类的公共父类,可以是抽象类也可以是接口,甚至还可以是具体实现类。
- ConcretePrototype(具体原型类):它实现在抽象原型类中声明的克隆方法,在克隆方法中返回自己的一个克隆对象。
- Client(客户类):让一个原型对象克隆自身从而创建一个新的对象,在客户类中只需要直接实例化或通过工厂方法等方式创建一个原型对象,再通过调用该对象的克隆方法即可得到多个相同的对象。
2.浅拷贝
如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。简单来说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。
public class Student implements Cloneable{
private String name;
private int age;
private String color;
private IdCard idCard;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public IdCard getIdCard() {
return idCard;
}
public void setIdCard(IdCard idCard) {
this.idCard = idCard;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", color='" + color + '\'' +
", idCard=" + idCard +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class IdCard {
private String no;
private String address;
public String getNo() {
return no;
}
public void setNo(String no) {
this.no = no;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "IdCard{" +
"no='" + no + '\'' +
", address='" + address + '\'' +
'}';
}
}
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
Student student = new Student();
student.setName("张三");
student.setAge(18);
IdCard idCard = new IdCard();
idCard.setNo("123");
idCard.setAddress("四川");
student.setIdCard(idCard);
System.out.println(student);
Student student1 = (Student) student.clone();
System.out.println(student1);
System.out.println(student == student1);
System.out.println(student.getIdCard() == student1.getIdCard());
}
}
由运行结果知道,原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,对象是一样的。
3.深拷贝
无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。
public class DeepClone implements Serializable {
private static final long serialVersionUID = 6319963435178724617L;
private String name;
private int age;
private String color;
private IdCard idCard;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public IdCard getIdCard() {
return idCard;
}
public void setIdCard(IdCard idCard) {
this.idCard = idCard;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", color='" + color + '\'' +
", idCard=" + idCard +
'}';
}
protected DeepClone deepClone() throws IOException, ClassNotFoundException {
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos)) {
oos.writeObject(this);
try (ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis)) {
return (DeepClone) ois.readObject();
}
}
}
}
public class IdCard implements Serializable {
private static final long serialVersionUID = 7625090532242404161L;
private String no;
private String address;
public String getNo() {
return no;
}
public void setNo(String no) {
this.no = no;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "IdCard{" +
"no='" + no + '\'' +
", address='" + address + '\'' +
'}';
}
}
public class Client {
public static void main(String[] args) throws IOException, ClassNotFoundException {
DeepClone deepClone = new DeepClone();
deepClone.setName("张三");
deepClone.setAge(18);
IdCard idCard = new IdCard();
idCard.setNo("123");
idCard.setAddress("四川");
deepClone.setIdCard(idCard);
System.out.println(deepClone);
DeepClone deepClone1 = deepClone.deepClone();
System.out.println(deepClone1);
System.out.println(deepClone == deepClone1);
System.out.println(deepClone.getIdCard() == deepClone1.getIdCard());
}
}
由结果可知,原型的引用类型,复制一份给克隆对象。深克隆技术实现了原型对象和克隆对象的完全独立,对任意克隆对象的修改都不会给其他对象产生影响,是一种更为理想的克隆实现方式。
六、建造者模式
1.概念
建造者模式(Builder Pattern):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。建造者模式是一种对象创建型模式。
建造者模式一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。
- Builder(抽象建造者):它为创建一个产品Product对象的各个部件指定抽象接口,在该接口中一般声明两类方法,一类方法是buildPartX(),它们用于创建复杂对象的各个部件;另一类方法是getResult(),它们用于返回复杂对象。Builder既可以是抽象类,也可以是接口。
- ConcreteBuilder(具体建造者):它实现了Builder接口,实现各个部件的具体构造和装配方法,定义并明确它所创建的复杂对象,也可以提供一个方法返回创建好的复杂产品对象。
- Product(产品角色):它是被构建的复杂对象,包含多个组成部件,具体建造者创建该产品的内部表示并定义它的装配过程。
- Director(指挥者):指挥者又称为导演类,它负责安排复杂对象的建造次序,指挥者与抽象建造者之间存在关联关系,可以在其construct()建造方法中调用建造者对象的部件构造与装配方法,完成复杂对象的建造。客户端一般只需要与指挥者进行交互,在客户端确定具体建造者的类型,并实例化具体建造者对象(也可以通过配置文件和反射机制),然后通过指挥者类的构造函数或者Setter方法将该对象传入指挥者类中。
2.使用
在我们经常玩的LOL中,有众多的英雄,这些英雄是一个复杂的对象,无论哪个英雄角色,它的创建都大同小异,我们用建造者进行问题解决。
public interface HeroBuild {
void buildName();
void buildSex();
void buildPosition();
Hero getResult();
}
public class Hero {
private String name;
private String sex;
private String position;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getPosition() {
return position;
}
public void setPosition(String position) {
this.position = position;
}
@Override
public String toString() {
return "Hero{" +
"name='" + name + '\'' +
", sex='" + sex + '\'' +
", position='" + position + '\'' +
'}';
}
}
public class GailunBuilder implements HeroBuild {
private Hero hero = new Hero();
@Override
public void buildName() {
hero.setName("盖伦");
}
@Override
public void buildSex() {
hero.setSex("男");
}
@Override
public void buildPosition() {
hero.setPosition("上单");
}
@Override
public Hero getResult() {
return hero;
}
}
public class VnBuilder implements HeroBuild{
private Hero hero = new Hero();
@Override
public void buildName() {
hero.setName("维恩");
}
@Override
public void buildSex() {
hero.setSex("女");
}
@Override
public void buildPosition() {
hero.setPosition("下路");
}
@Override
public Hero getResult() {
return hero;
}
}
public class Director {
private HeroBuild heroBuild;
public Director(HeroBuild heroBuild) {
this.heroBuild = heroBuild;
}
public Hero construct() {
heroBuild.buildName();
heroBuild.buildSex();
heroBuild.buildPosition();
return heroBuild.getResult();
}
}
public class Client {
public static void main(String[] args) {
Hero gailun = new Director(new GailunBuilder()).construct();
System.out.println(gailun);
Hero vn = new Director(new VnBuilder()).construct();
System.out.println(vn);
}
}
建造者模式的主要优点如下:
- 在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
- 每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对象。由于指挥者类针对抽象建造者编程,增加新的具体建造者无须修改原有类库的代码,系统扩展方便,符合“开闭原则”
- 可以更加精细地控制产品的创建过程。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
建造者模式的主要缺点如下:
- 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,例如很多组成部分都不相同,不适合使用建造者模式,因此其使用范围受到一定的限制。
- 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大,增加系统的理解难度和运行成本。
在以下情况下可以考虑使用建造者模式:
- 需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员属性。
- 需要生成的产品对象的属性相互依赖,需要指定其生成顺序。
- 对象的创建过程独立于创建该对象的类。在建造者模式中通过引入了指挥者类,将创建过程封装在指挥者类中,而不在建造者类和客户类中。
- 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。
总结
以上就是几种创建型模式:简单工厂模式、工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式,通过本文学习我们能快速掌握这几种模式。