实验内容重点看,无需死记,它更是一种设计思想。要理解一种设计模式出现的意义是什么,它又是如何方便我们使用的?目的无非就是解耦、易扩充。题目问到优缺点,你只要知道该模式的设计思想就完全可以用自己的话概述,所以还是不用死记,全书围绕面向对象设计原则进行编程,也就是课本P26页的表格。
本来这门课不打算出博客了,现在又决定还是出一下吧,反正写博客的时候自己也就跟着顺便复习了,重要的章节我会放源码,不重要的只放核心思路,本篇内容是根据自己的理解写的,总结不易,进来的小伙伴希望能留个攒,感谢!
文章目录
- 一、创建型模式
- 1. 简单工厂模式
- 2. 工厂方法模式
- 3. 抽象工厂模式
- 4. 建造者模式
- 5. 原型模式
- 6. 单例模式
一、创建型模式
1. 简单工厂模式
工厂是用来干什么的?在我们的生活中工厂当然是用来生产产品的,但在 Java 中,工厂是用来生产对象的,如何生产?内部原理其实还是 new 了一个具体的实现类。
为什么叫简单工厂模式呢?简单是因为它是静态的,何为静态?静态即关键字 static,工厂方法用 static 修饰后,测试类中就直接可以通过类名调用方法,而无需创建工厂对象。
我们明明可以直接在测试类中 new 一个对象,调用方法就可以了,为什么还要用到这工厂类呢?这里正是简单工厂模式出现的意义:
当你需要什么,只需要传入一个正确的参数,就可以获取你所需要的对象,而无需知道其创建细节,即在测试类中无需创建对象,只负责传入产品名称即可。
整体结构:
① 一个抽象产品类,里面有一个抽象方法;
② 数个具体产品类,重写抽象产品类中的抽象方法;
③ 一个工厂类,里面有一个静态方法,通过条件分支对传入的参数做不同的判断,并返回所对应的产品对象;
④ 一个测试类,通过工厂类调用其静态方法,并传入参数,返回的结果就是该参数所对应的产品对象,产品对象调用其内置方法,并输出结果。
这里有一个小的问题,当我们每次生产不同的产品都需要在测试类中传入不同的参数,这是不符合开闭原则的,就是说我测试类中的东西你不要随意地动,这时候就想,我们能不能把它做成死的以后就固定住了,所以在此我们引入一个工具类 XMLUtil,通过它可以从 XML 格式的配置文件中读取节点获取数据,如需修改产品名称,无需修改客户端代码,只需修改配置文件,通过配置文件可以极大地提高系统的扩展性,让软件实体更符合开闭原则。
此工具类我们会一直使用,后面再出现就不多做解释,但是放心考试是不会考工具类的。
//简单工厂类
public class PhoneFactory {
public Phone makePhone(String phoneType){
if(phoneType.equalsIgnoreCase("XM")){
return new XmPhone();
}
else if(phoneType.equalsIgnoreCase("PG")){
return new PgPhone();
}
return null;
}
}
2. 工厂方法模式
在上述简单工厂模式中存在诸多的缺点,最明显的就是它把所有产品对象的创建过程都放在一个工厂类里面,万一我们其中一个地方出错呢?系统就全盘崩溃了,万一我要增加新的产品呢?你还要去修改工厂类中的条件分支语句,这有违开闭原则。
对此,工厂方法模式帮我们解决这些问题。解决办法就是使工厂类更加抽象,新增一个抽象工厂类,只提供一个公共的生产方法,而具体的创建工作交给子工厂类去做。
当系统扩展需要添加新的产品对象时,仅仅需要添加一个具体产品对象以及一个具体工厂对象即可,原有对象不需要进行任何修改,也不需要修改客户端,即对外扩展开放,对内修改关闭。
整体结构:
① 一个抽象产品类;
② 数个具体产品类;
③ 一个抽象工厂类,内置公共生产方法;
④ 数个具体工厂类,每个具体工厂负责生产一种产品。
//抽象工厂类
public interface TVFactory {
public TV createTV();
}
//具体工厂类
public class HaierFactory implements TVFactory {
public TV createTV() {
return new HaierTV();
}
}
3. 抽象工厂模式
我觉得抽象工厂模式应该是工厂方法模式的升级版。
此话怎讲?上面的工厂方法模式,一个工厂里面只能生产一种产品,这能合理吗?工厂怎么可能只生产一种产品呢,不管你是海尔品牌还是 TCL 品牌,除了生产电视机,我是不是还可以生产冰箱、生产空调,这合情合理吧?
怎么做呢?你既然要增加新的产品,那么就多写几个产品抽象类,并实现。
抽象工厂类中也要新增生产方法,之前只生产电视,现在请增加几个其它产品的生产方法,至于增加几个?你有几种产品就增加几个呗,然后让具体工厂类实现该抽象工厂类,最终返回其所对应的产品对象。
具体产品在应用层的代码被隔离,无需关心创建的细节,将一系列的产品统一到一起去创建,如果需要增加新的工厂,直接继承抽象工厂类即可。但是缺点也很明显,如果需要增加新的产品就意味着要彻底修改所有的工厂类对象,扩展是很困难的,增加了系统的抽象性和理解难度。
整体结构:
① 数个抽象产品类;
② 数个具体产品类;
③ 一个抽象工厂类,内置数个公共生产方法;
④ 数个具体工厂类,每个具体工厂负责生产它自己旗下的数个产品。
//抽象产品类
public abstract class CPU {
public abstract void isCPU();
}
public abstract class RAM {
public abstract void isRAM();
}
//具体产品类
public class MacCPU extends CPU {
@Override
public void isCPU() {
System.out.println("是Mac的CPU!");
}
}
public class PcCPU extends CPU {
@Override
public void isCPU() {
System.out.println("是Pc的CPU!");
}
}
public class MacRAM extends RAM {
@Override
public void isRAM() {
System.out.println("是Mac的RAM!");
}
}
public class PcRAM extends RAM {
@Override
public void isRAM() {
System.out.println("是Pc的RAM!");
}
}
//抽象工厂类
public abstract class AbstractFactory {
public abstract CPU produceCPU();
public abstract RAM produceRAM();
}
//具体工厂类
public class PcFactory extends AbstractFactory {
@Override
public CPU produceCPU() {
return new PcCPU();
}
public RAM produceRAM() {
return new PcRAM();
}
}
public class MacFactory extends AbstractFactory {
@Override
public CPU produceCPU() {
return new MacCPU();
}
public RAM produceRAM() {
return new MacRAM();
}
}
4. 建造者模式
首先想一下建造者是干什么的?你说那还用问吗自然是建设与创造啊。以课本例子为例,当你去饭店吃饭的时候服务员会给你推荐几个套餐,不同套餐里的食物也会不一样,而每一份套餐我们都会有特定的人去生产,这些制作套餐的对象我们称之为建造者,建造者有很多,一个建造者对应一种套餐。
还要有一个服务员,由他来接收客户需求,并传达套餐需求给建造者。
套餐类用于将传入的套餐信息封装成一个完整的菜单,里面有两个方法,一个吃一个喝,以及各自的 get、set 方法,用于后续的传参取参操作。
在抽象建造者类中,第一步需要 new 一个套餐对象,因为建造的过程就是给套餐类传参的过程,接下来给出两个抽象方法,用于建造食物和饮品。我们要想在控制台输出套餐结果,就得先获取到套餐对象,在抽象建造者类里再提供一个方法用于返回 meal 对象。
具体建造者类依次继承抽象建造者类并实现其所有方法,其实就是调用套餐类的 set 方法进行传参,这些传入的参数就是套餐内容。
我们本来可以在测试类中直接调用建造者类中的 buildFood、buildDrink、getMeal 方法进行传参以及获取套餐对象,但如果把这些过程全都写在测试类中,这还像是设计模式的思想吗?对于客户端我们尽量是不要暴露太多内部操作的,所以这里我们又引入一个指挥者类。
首先,指挥者类中要给出一个 setMealBuilder 方法,测试类会通过调用该方法进行传参,此参正是套餐类型,同时再写一个 construct 方法,在该方法里面我们要进行对 buildFood 和 buildDrink 的调用,最后返回抽象建造者类中的 getMeal 方法,即返回一个 meal 对象,调用 meal 的 get 方法将套餐内容输出在控制台。
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
整体结构:
① 一个复杂产品类,数个成员属性以及各自的 get、set 方法;
② 一个抽象建造者类;
③ 数个具体建造者类,调用产品类的 set 方法并传参;
④ 一个指挥者类,将客户端的调用请求都封装在了一个类中,保证了客户端对外的封闭性。
//产品类
public class Meal {
private String food;
private String drink;
public String getFood() {
return food;
}
public void setFood(String food) {
this.food = food;
}
public String getDrink() {
return drink;
}
public void setDrink(String drink) {
this.drink = drink;
}
}
//抽象建造者类
public abstract class MealBuilder {
Meal meal = new Meal();
public abstract void buildFood();
public abstract void buildDrink();
public Meal getMeal(){
return meal;
}
//具体建造者类
public class MealA extends MealBuilder{
public void buildDrink() {
meal.setDrink("汉堡");
}
public void buildFood() {
meal.setFood("果汁");
}
}
//指挥者类
public class KFCWaiter {
private MealBuilder mealBuilder;
public KFCWaiter(MealBuilder mealBuilder) {
this.mealBuilder = mealBuilder;
}
public Meal construct(){
mealBuilder.buildFood();
mealBuilder.buildDrink();
return mealBuilder.getMeal();
}
}
5. 原型模式
原型即原始的模型,那么它出现的场合是啥呢?
当需要创建大量相同或者相似的对象时,可以通过对一个已有对象的复制获取更多对象。也就是说我们只需要创建一个原型对象,然后通过在类中定义的克隆方法来复制自己,当创建新对象的成本较大时,原型模式可以极大地提高创建新实例对象的效率。
整体结构:
① 一个抽象原型类,内置抽象克隆方法;
② 一个具体原型类。
浅克隆可以使用Java自带的克隆方法,需实现 Cloneable 接口,浅克隆只复制值类型的变量,不复制引用类型,所以克隆前后的 WeeklyLog 是两个完全不同的对象,但其内部的成员对象Attachment是同一个对象。
//具体原型类,浅克隆
public class WeeklyLog implements Cloneable{
//getter,setter方法省略
private Attachment attachment;
private String name;
private String date;
//浅克隆核心代码
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
深克隆可以通过实现序列化接口,将对象写入流中,再从流中进行读取的方法实现,需实现 Serializable 接口,深克隆对于值类型变量和引用类型变量都会复制,所以克隆前后的 WeeklyLog 和 Attachment 都已不是同一个对象。
//具体原型类,深克隆
public class WeeklyLog implements Serializable {
public WeeklyLog deepClone() throws IOException, ClassNotFoundException {
//getter,setter方法省略
private Attachment attachment;
private String name;
private String date;
//深克隆核心代码
//将对象写入到流中
ByteArrayOutputStream baos=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(baos);
oos.writeObject(this);
//将对象从流中取出
ByteArrayInputStream bis=new ByteArrayInputStream(bao.toByteArray());
ObjectInputStream ois=new ObjectInputStream(bis);
return (WeeklyLog)ois.readObject();
}
}
6. 单例模式
顾名思义,单例指的是单个的实例,它是,所有设计模式中结构最为简单的模式。
单例模式要确保一个类中只有一个实例,且自行实例化,这个类称为单例类,它提供全局访问的方法。
注意:
① 单例类的构造函数为私有,阻止外部通过构造函数创建对象;
② 提供一个自身的静态私有成员变量,为什么是静态的?因为该成员变量可以说是一个标识,我们想用它来判断一个对象是否已经存在,就必须把它设为 static,保证每次操作之后的变量值不会恢复,内部条件判断语句判断该变量值是否为空,如果为空就代表对象不存在,接下来 new 一个对象并为其设置参数,这个时候的成员变量值已经不是空了,当下次在客户端再调用该方法时,由于成员变量值不为空,将走 else 路线告诉客户端对象已经存在不允许重复创建;
③ 提供一个公有的静态工厂方法,共有的静态方法是为了客户端可以直接通过类名调用方法,而无需再创建类对象。
既然知道了单例模式,那多例模式你能想到吗?没错,以下正是我们实验里面的内容,我们能不能想办法保证一个类只能创建固定数量的对象呢,单例模式通过是否为空来控制,同样道理,多例模式我们完全可以用数字来控制。
就是说在一个类中只允许存在有限个实例对象,在判断的时候让创建对象的次数小于某个值就可以,小于就创建新对象,如果超出设定个数就不会再创建。同时给出私有的无参构造方法,使外部不能通过new来实例化对象,即只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。
public class Multiton {
private static Multiton array;
private static int i = 1;
private Multiton() {
}
public static Multiton getInstance() {
int count = random();
if (count <= 3) {
array = new Multiton();
System.out.println("第" + count + "个对象!");
} else {
System.out.println("只允许创建三个对象!");
}
return array;
}
public static int random() {
return i++;
}
}
public class Client {
public static void main(String[] args) {
Multiton m1 = Multiton.getInstance();
Multiton m2 = Multiton.getInstance();
Multiton m3 = Multiton.getInstance();
Multiton m4 = Multiton.getInstance();
}
}