一、Singleton 模式-只有一个实例
1. 需求
这里 Singleton 作为学习 javase 的一部分,不再强调实例代码,而是这种模式的思想。
Singleton 单例模式,我们在 spring framework 中可以 通过 scope 指定 一个 Bean 的作用域,就包含了这个思想。
实现的关键的是,将构造器私有化,禁止从 Singleton 类之外调用构造器。
2.Code
2.1 饿汉式【Eager Singleton】 (线程安全,调用效率高,但是不能延时加载)
/**
* 饿汉式:迫切
* @author CSZ
*/
public class EagerSingleton {
/**
* 私有化静态属性,随着类的加载而加载,所以是饿汉式
*/
private static final EagerSingleton INSTANCE = new EagerSingleton();
/**
* 私有化构造器
*/
private EagerSingleton(){}
/**
* 给外界提供一个获取类实例的方法
* @return 类的实例
*/
public static EagerSingleton getInstance(){
return INSTANCE;
}
}
2.2 懒汉式【Lazy Singleton】(线程安全,调用效率不高,但是能延时加载)
/**
* 懒汉式:不着急
* @author CSZ
*/
public class LazySingleton {
/**
* 类初始化时,不初始化这个对象(延时加载,真正用的时候再创建)
*/
private static LazySingleton instance;
/**
* 构造器私有化
*/
private LazySingleton(){}
/**
* 方法使用 synchronized 同步,调用效率低
* @return
*/
public static synchronized LazySingleton getInstance() {
// 这里做了简单判断,如果为空说明首次创建,如果不为空,单例对象,直接返回即可
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
2.3 静态内部类实现模式(线程安全,调用效率高,可以延时加载)
/**
* 使用 静态内部类
* @author CSZ
*/
public class LazySingletonPlus {
private static class SingletonClassInstance{
private static final LazySingletonPlus instance=new LazySingletonPlus();
}
private LazySingletonPlus(){}
public static LazySingletonPlus getInstance(){
return SingletonClassInstance.instance;
}
}
2.4 枚举类实现(线程安全,调用效率高,不能延时加载,可以天然的防止反射和反序列化调用)
/**
* @author CSZ
*/
public enum SingletonEnum {
/**
* 枚举元素本身就是单例
*/
INSTANCE;
/**
* 添加自己需要的操作
*/
public void singletonOperation(){
}
}
简单测试
/**
* @author CSZ
*/
public class MainTest {
public static void main(String[] args) {
System.out.println("start.");
EagerSingleton instance1 = EagerSingleton.getInstance();
EagerSingleton instance2 = EagerSingleton.getInstance();
if (instance1 == instance2) {
System.out.println("相同实例");
}else {
System.out.println("不同实例");
}
System.out.println("End.");
}
}
二、Prototype 模式-通过复制生成实例
1. 场景分析
Prototype 模式,原型模式,以往我们实例的创建是通过调用 类的构造器,并且为属性赋值的方式,但是在开发中,我们也会出现“不指定类名的前提下,生成实例”的需求:
- 对象种类繁多,无法整合到一个类中
- 难以根据类生成实例,比如一个复杂的图形界面
- 想解耦框架与生成的实例时
书中举了一个栗子:
我们使用实例生成实例的过程类似于,通过复制的方式,复制文档。此时即使我们不了解原来文档的内容,我们也可以使用 复印机复制出相同的文档。
2. 案例分析
3. UML
4. Code
4.1 Manager(使用者),负责使用复制实例的方法生成新的实例。
/**
* @author CSZ
*/
public class Manager {
// 回顾一下Java的知识点,final 修饰成员变量,变量可以分为两种,一种是基本变量,一种是引用变量
// 基本变量 要在构造器或者代码块中赋值
// 引用变量,则是规定了引用地址在运行时不许改变。也就是不会引用新的对象
private final HashMap<String,Product> showcase = new HashMap<>();
public void register(String name, Product proto){
showcase.put(name,proto);
}
public Product create(String protoName){
Product product = showcase.get(protoName);
return product.createClone();
}
}
4.2 Product(抽象的原型)接口负责定义复制现有实例来生成新的实例的方法
/**
* @author CSZ
*/
public interface Product extends Cloneable {
// public abstract 默认可省略,显示写出方便大家理解
public abstract void use(String s);
// 看到下面的方法有没有感觉很妙啊,Product 继承了Cloneable,所以Product 具备了 clone() 的能力。
// 1)抽象了这个方法,要求所有的实现类必须重写该方法,延迟了实现。
// 2)返回的类型必须是 Product 类型,所有的实现类,在内部必须创建 Product 对象的"实例"。
public abstract Product createClone();
}
4.3 MessageBox(具体的原型)针对抽象原型的定义方法做实现。
public class MessageBox implements Product{
private final char decochar;
public MessageBox(char decochar){
this.decochar = decochar;
}
@Override
public void use(String s) {
int length = s.getBytes().length;
for (int i = 0; i < length + 4; i++) {
System.out.print(decochar);
}
System.out.println("");
System.out.println(decochar + " " + s + " " + decochar);
for (int i = 0; i < length + 4 ; i++) {
System.out.print(decochar);
}
System.out.println("");
}
@Override
public Product createClone() {
Product product = null;
try {
product = (Product)clone();
}catch (CloneNotSupportedException e){
e.printStackTrace();
}
return product;
}
}
UnderlinePen(具体的原型)针对抽象原型的定义方法做实现。
/**
* @author CSZ
*/
public class UnderlinePen implements Product {
private final char ulchar;
public UnderlinePen(char ulchar) {
this.ulchar = ulchar;
}
@Override
public void use(String s) {
int length = s.getBytes().length;
System.out.println("\"" + s + "\"");
System.out.print("");
for (int i = 0; i < length; i++) {
System.out.print(ulchar);
}
System.out.println("");
}
@Override
public Product createClone() {
Product product = null;
try {
// 跟另一个实现类稍有区别,但是没有写错,编译运行通过的哦
// 这里想强调的就是,我们的模板就是对象本身。
product = (Product)this.clone();
}catch (CloneNotSupportedException e){
e.printStackTrace();
}
return product;
}
}
4.5 MainTest 测试方法
public class MainTest {
public static void main(String[] args) {
// 准备工作
// 创建原型对象,是不是感觉很熟悉,有点像 spring 中声明注册bean的过程。
// 进一步的体会,
Manager manager = new Manager();
UnderlinePen uPen = new UnderlinePen('~');
MessageBox mBox = new MessageBox('*');
MessageBox sBox = new MessageBox('/');
manager.register("strong message",uPen);
manager.register("warning box",mBox);
manager.register("slash box",sBox);
// 生成
Product product1 = manager.create("strong message");
product1.use("Hello World.");
Product product2 = manager.create("warning box");
product2.use("Hello World.");
Product product3 = manager.create("slash box");
product3.use("Hello World.");
}
}
5. 结合图片思考与分析
- 在这个设计模式中,我们说明了 Manager 的作用是负责使用复制实例的方法生成新的实例:
showcase 字段 是一个 Map 集合,里面保存了名字和实例之间的映射关系。
register 方法 将接受的一组 <名字,Product接口的实现类对象> 注册到 showcase 中。没有规定成具体的类,这就为我们的解耦提供了可能,所以我们在 framework 包中的类和接口中完全不涉及两个实现类的信息。 - 在进一步,在 javase 的学习中,我们最习惯的方式 是 new 一个对象,可是如果是一个复杂的对象,参数需要大量时间准备(比如需要在数据库获取),此时如果有一个类似复印机的方式是不是更简单,也更容易实现。
- 最后对于 clone() 做一个简单的说明,clone() 可以得到是一个浅复制,如果想要实现深复制还需要重写
clone() 方法可以参考 https://blog.csdn.net/qq_41409138/article/details/86762163。
三、Builder 模式-组装复杂的实例
1. 需求分析
2. UML
3. Code
3.1 Builder 扮演建造者的角色,定义了用于生成实例的方法(接口API)
比如:Builder 就是土木工程里面的图稿设计者
/**
* @author CSZ
*/
public abstract class Builder {
public abstract void makeTitle(String title);
public abstract void makeString(String string);
public abstract void makeItems(String[] items);
public abstract void close();
}
3.2 Director 扮演监工的角色,通过has-a 的形式,获取到 Builder 对象
比如:Director 作为工程师,根据图稿设计者选取合适的材料。
/**
* @author CSZ
*/
public class Director {
private final Builder builder;
public Director(Builder builder) {
this.builder = builder;
}
public void construct(){
builder.makeTitle("Greeting");
builder.makeString("从早上至下午");
builder.makeItems(new String[]{"早上好","下午好"});
builder.makeString("晚上");
builder.makeItems(new String[]{"晚上好","晚安","再见"});
builder.close();
}
}
3.3 TextBuilder 具体建造者1
相当于包工头:工程师根据图纸和材料进行现场调度,真正的执行需要通过包工头实现。
/**
* @author CSZ
*/
public class TextBuilder extends Builder {
private StringBuffer buffer = new StringBuffer();
@Override
public void makeTitle(String title) {
buffer.append("========================\n");
buffer.append("[").append(title).append("]\n");
buffer.append("\n");
}
@Override
public void makeString(String string) {
buffer.append("■").append(string).append("\n");
buffer.append("\n");
}
@Override
public void makeItems(String[] items) {
for (String item : items) {
buffer.append(" ·").append(item).append("\n");
}
buffer.append("\n");
}
@Override
public void close() {
buffer.append("========================\n");
}
public String getResult(){
return buffer.toString();
}
}
3.4 HTMLBuilder 具体建造者2
/**
* @author CSZ
*/
public class HTMLBuilder extends Builder {
private String fileName;
private PrintWriter writer;
@Override
public void makeTitle(String title) {
fileName = title + ".html";
try {
PrintWriter writer = new PrintWriter(new FileWriter(fileName));
}catch (IOException e){
e.printStackTrace();
}
writer.println("<html><head><title>" + title + "</title></head><body>");
}
@Override
public void makeString(String string) {
writer.println("<p>" + string + "</p>" );
}
@Override
public void makeItems(String[] items) {
writer.println("<ul>");
for (String item : items) {
writer.println("<li>" + item + "</li>");
}
writer.println("</ul>");
}
@Override
public void close() {
writer.println("</body></html>");
writer.close();
}
public String getResult(){
return fileName;
}
}
3.5 测试类
/**
* @author CSZ
*/
public class MainTest {
public static void main(String[] args) {
if (args.length != 1){
usage();
System.exit(0);
}
if ("plain".equals(args[0])){
TextBuilder textBuilder = new TextBuilder();
Director director = new Director(textBuilder);
director.construct();
String result = textBuilder.getResult();
System.out.println(result);
}else if ("html".equals(args[0])){
HTMLBuilder htmlBuilder = new HTMLBuilder();
Director director = new Director(htmlBuilder);
director.construct();
String result = htmlBuilder.getResult();
System.out.println("文档编写完成");
}else {
usage();
System.exit(0);
}
}
public static void usage(){
System.out.println("Usage:java Main plain 纯文本");
System.out.println("Usage:java Main HTML 网页");
}
}
4. 思考
// ToDo 暂时还没有特别深的印象,只是记得在实习的时候,前辈有个模块使用了该模式。
四、Abstract Factory(抽象工厂)-- 将关联零件组装成产品
1. 场景分析
2. UML
3. 最终效果
4. Code
4.1 包结构
4.2 Factory 抽象工厂
/**
* @author CSZ
*/
public abstract class Factory {
public static Factory getFactory(String className){
Factory factory = null;
try {
factory = (Factory)Class.forName(className).getDeclaredConstructor().newInstance();
}catch (ClassNotFoundException | NoSuchMethodException ignored){
System.out.println("没有找到" + className + "类");
} catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
e.printStackTrace();
}
return factory;
}
public abstract Link createLink(String caption,String url);
public abstract Tray createTray(String caption);
public abstract Page createPage(String title ,String author);
}
4.3 整合了其中的零件,每个public 类是一个 .java 文件
/**
* 抽象Item
* @author CSZ
*/
public abstract class Item {
protected String caption;
public Item(String caption) {
this.caption = caption;
}
public abstract String makeHTML();
}
/**
* Link 继承自 Item
* @author CSZ
*/
public abstract class Link extends Item{
protected String url;
public Link(String caption,String url){
super(caption);
this.url = url;
}
}
/**
* Tray 也继承自 Item
* @author CSZ
*/
public abstract class Tray extends Item{
protected ArrayList<Item> tray = new ArrayList<>();
public Tray(String caption) {
super(caption);
}
public void add(Item item){
tray.add(item);
}
}
/**
* 用于整合填充的HTML 页面
* @author CSZ
*/
public abstract class Page {
protected String title;
protected String author;
protected ArrayList<Item> content = new ArrayList<>();
public Page(String title,String author){
this.title = title;
this.author = author;
}
public void add(Item item){
content.add(item);
}
// 定义了最终组装成页面逻辑。
public abstract String makeHTML();
// 页面保存逻辑。
public void output(){
try {
String filename = title + ".html";
FileWriter writer = new FileWriter(filename);
writer.write(this.makeHTML());
writer.close();
System.out.println(filename + "编写完成");
}catch (IOException e){
e.printStackTrace();
}
}
}
4.4 ListFactory 实际工厂
/**
* @author CSZ
*/
public class ListFactory extends Factory{
@Override
public Link createLink(String caption, String url) {
return new ListLink(caption,url);
}
@Override
public Tray createTray(String caption) {
return new ListTray(caption);
}
@Override
public Page createPage(String title, String author) {
return new ListPage(title,author);
}
}
4.5 整合各种组件 每个 public 类是一个 .java 文件
/**
* ListLink 调用父类的构造器,并且重写了 makeHTML 方法
* @author CSZ
*/
public class ListLink extends Link {
public ListLink(String caption,String url){
super(caption,url);
}
@Override
public String makeHTML() {
return "<li<a href=\"" + url + ">" + caption + "</a></li>\n";
}
}
/**
* ListTray 继承自Tray,Tray 又继承自 Item
* @author CSZ
*/
public class ListTray extends Tray {
public ListTray(String caption) {
super(caption);
}
@Override
public String makeHTML() {
StringBuffer buffer = new StringBuffer();
buffer.append("<li\n");
buffer.append(caption).append("\n");
buffer.append("<ul>\n");
for (Item item : tray) {
buffer.append(item.makeHTML());
}
buffer.append("</ul>\n");
buffer.append("</li>\n");
return buffer.toString();
}
}
/**
* ListPage 继承自 Page,重写了页面组装逻辑
* @author CSZ
*/
public class ListPage extends Page{
public ListPage(String title, String author) {
super(title, author);
}
@Override
public String makeHTML() {
StringBuffer buffer = new StringBuffer();
buffer.append("<html><head><title>").append(title).append("</title></head>\n");
buffer.append("<body>\n");
buffer.append("<h1>").append(title).append("</h1>\n");
buffer.append("<ul>\n");
for (Item item : content) {
buffer.append(item.makeHTML());
}
buffer.append("<ul>\n");
buffer.append("<hr><address>").append(author).append("</address>");
buffer.append("</body></html>\n");
return buffer.toString();
}
}
4.6 测试类
/**
* @author CSZ
*/
public class Main {
public static void main(String[] args) {
if (args.length != 1){
System.out.println("Usage:java Main class.name.of.ConcreteFactory");
System.out.println("Example 1: java Main listfactory.ListFactory");
System.out.println("Example 2: java Main tablefactory.TableFactory");
System.exit(0);
}
Factory factory = Factory.getFactory(args[0]);
Link people = factory.createLink("人民日报", "http://www.people.com.cn/");
Link gmw = factory.createLink("光明日报", "http://www.gmw.cn/");
Link us_yahoo = factory.createLink("Yahoo!", "http://www.yahoo.com/");
Link jp_yahoo = factory.createLink("Yahoo!Japan", "http://www.yahoo.jp/");
Link excite = factory.createLink("Excite", "http://www.excite.com/");
Link google = factory.createLink("google", "http://www.google.com/");
Tray trayNews = factory.createTray("日报");
trayNews.add(people);
trayNews.add(gmw);
Tray trayYahoo = factory.createTray("Yahoo!");
trayYahoo.add(us_yahoo);
trayYahoo.add(jp_yahoo);
Tray traySearch = factory.createTray("搜索引擎");
traySearch.add(trayYahoo);
traySearch.add(excite);
traySearch.add(google);
Page page = factory.createPage("LinkPage", "chang");
page.add(trayNews);
page.add(traySearch);
page.output();
}
}
4. 总结与思考
其实大家看到这个场景也很容易理解这种设计模式的优缺点:
优点:当我们想扩展新的模块,比如想支持华为手机的市场,我们只需要创建一个似于 listfactory 包下的内容就可以。其中的 main 方法的入参变成你期待的 新工厂就好了,抽象的工厂也不需要修改。
缺点:假设有一天,我们手机大变革,之前的步骤不适用了,即 抽象工厂的步骤有问题,比如多了一道工序,旗下的所有工厂都需要进行返工,都添加上这步操作。