一、单例模式
单例是面试当中最常见的一种设计模式,但是我们在应用中一般都是借助 Spring 指定生成单例还是多例对象,所以手写的情况不多。此处列出几种单例模式的写法。
饿汉式
public class SingleTonTestApp {
private static SingleTonTestApp singleTon = new SingleTonTestApp();
private SingleTonTestApp() {
}
public static SingleTonTestApp getInstance() {
return singleTon;
}
}
- 优点:不会存在线程安全问题
- 缺点:如果构造方法中存在过多的处理,会导致该类加载的时候特别慢,因此可能会引起性能问题。如果仅仅进行了类的加载,但没有进行类的应用,会造成比较大的资源浪费
总结
如果一个类,构造函数中的处理不繁杂,并且该类的单例实例一定会应用到,那么就选择饿汉式。
懒汉式
public class SingletonTestApp {
private static volatile SingletonTestApp singleton = null;
private SingletonTestApp() {
}
public static SingletonTestApp getSingleton() {
if (singleton == null) {
synchronized (SingletonTestApp.class) {
if (singleton == null) {
singleton = new SingletonTestApp();
}
}
}
return singleton;
}
}
解析
1、 最内层的if语句最容易理解:如果singleton为空,则进行实例化
2、 由于多线程并发存在实例化多个singleton的问题,所以要在最内层if外加一个同步代码块,以保证同一时刻只能有一条线程进行if判断,防止多条线程同时进行实例化
3、 最外层if根本目的在于提高性能:如果singleton不为空,则直接返回singleton。就算是多条线程同时进行singleton是否为空的判断也是没有问题的,这样就无需进入同步代码块排队、实例化,直接返回不为空的singleton。
枚举式
public class SingletonTestApp {
private SingletonTestApp(){}
//对外提供一个工厂方法获取实例对象,因为只能通过类名调用,所以用static修饰
public static SingletonTestApp getInstance() {
return SingletonEnum.INSTANCE.getSingleton();
}
/**
* 枚举必须是私有的
* ps:因为枚举类一开始就是有枚举实例(INSTANCE)的,所以里面的方法和成员都不用static去修饰
*/
private enum SingletonEnum {
//首先初始化一个枚举实例,目的是为了可以使用这个实例来创建需要单例的对象
INSTANCE;
//先声明单例对象的引用,节约资源
private SingletonTestApp singleton;
//JVM会保证单例的构造方法只会执行一次,从JVM层面上保证了线程安全
SingletonEnum() {
singleton = new SingletonTestApp();
}
//返回单例实例对象
SingletonTestApp getSingleton() {
return singleton;
}
}
}
解析
1、枚举的构造方法从JVM层面保证了线程安全
2、类加载的时候不会去执行枚举的构造函数,而是在调用getInstance的时候,先去实例化枚举对象(走枚举的构造函数),在调用枚举的getSinleton方法。属于懒加载,节约资源
单例模式的拓展
对象除了单例和多例外,还有一种是“有限的多例”。算是单例模式的一种拓展。它要求一个类只能产生两三个对象。
image.png
package com.baowen.nginx.structure.singleton;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;
public class Emperor {
/**
* 定义最多能产生的实例数量
*/
private static int maxNumOfEmperor = 2;
/**
* 每个皇帝都有名字, 使用一个ArrayList来容纳, 每个对象的私有属性
*/
private static List<String> nameList = new CopyOnWriteArrayList<>();
/**
* 定义一个列表, 容纳所有的皇帝实例
*/
private static List<Emperor> emperorList = new CopyOnWriteArrayList<Emperor>();
/**
* 当前皇帝序列号
*/
private static int countNumOfEmperor = 0;
//产生所有的对象
static {
for (int i = 0; i < maxNumOfEmperor; i++) {
emperorList.add(new Emperor("皇" + (i + 1) + "帝"));
}
}
private Emperor() {
}
/**
* 传入皇帝名称, 建立一个皇帝对象
*
* @param name
*/
private Emperor(String name) {
nameList.add(name);
}
/**
* 随机获得一个皇帝对象
*
* @return
*/
public static Emperor getInstance() {
Random random = new Random();
//随机拉出一个皇帝, 只要是个精神领袖就成
countNumOfEmperor = random.nextInt(maxNumOfEmperor);
return emperorList.get(countNumOfEmperor);
}
/**
* 皇帝发话了
*/
public static void say() {
System.out.println(nameList.get(countNumOfEmperor));
}
}
此处为了保证线程安全,可以使用 来替换书中的 ArrayList 和 Vector,Vector 也可以用,不过从原理上讲效率比较低。
二、工厂方法模式
2.1.1 书中案例
image.png/* 人类接口 */
public interface Human {
//每个人种的皮肤都有相应的颜色
public void getColor();
//人类会说话
public void talk();
}
/* 黑种人 */
public class BlackHuman implements Human {
public void getColor(){
System.out.println("黑色人种的皮肤颜色是黑色的! ");
}
public void talk() {
System.out.println("黑人会说话, 一般人听不懂。 ");
}
}
/* 黄种人 */
public class YellowHuman implements Human {
public void getColor(){
System.out.println("黄色人种的皮肤颜色是黄色的! ");
}
public void talk() {
System.out.println("黄色人种会说话, 一般说的都是双字节。 ");
}
}
/* 白种人 */
public class WhiteHuman implements Human {
public void getColor(){
System.out.println("白色人种的皮肤颜色是白色的! ");
}
public void talk() {
System.out.println("白色人种会说话, 一般都是但是单字节。 ");
}
}
/* ------------------------------------------------------------------*/
/* 抽象工厂 */
public abstract class AbstractHumanFactory {
public abstract <T extends Human> T createHuman(Class<T> c);
}
/* ------------------------------------------------------------------*/
/* 客户端业务类 */
public class HumanFactory extends AbstractHumanFactory {
public <T extends Human> T createHuman(Class<T> c){
//定义一个生产的人种
Human human = null;
try {
//产生一个人种
human = (T)Class.forName(c.getName()).newInstance();
} catch (Exception e) {
System.out.println("人种生成错误! ");
}
return (T)human;
}
}
在业务流程中,通过指定需要生成的实现类的类型,工厂就可以生成对应的对象,业务中拿来即用。
2.2 工厂方法的定义
image.png/* 抽象产品 */
public abstract class Product {
//产品类的公共方法
public void method1(){
//业务逻辑处理
}
//抽象方法
public abstract void method2();
}
/* 具体产品1 */
public class ConcreteProduct1 extends Product {
public void method2() {
//业务逻辑处理
}
}
/* 具体产品2 */
public class ConcreteProduct2 extends Product {
public void method2() {
//业务逻辑处理
}
}
/* 抽象工厂 */
public abstract class Creator {
/*
* 创建一个产品对象, 其输入参数类型可以自行设置
* 通常为String、 Enum、 Class等, 当然也可以为空
*/
public abstract <T extends Product> T createProduct(Class<T> c);
}
/* 具体工厂 */
public class ConcreteCreator extends Creator {
public <T extends Product> T createProduct(Class<T> c){
Product product=null;
try {
product = (Product)Class.forName(c.getName()).newInstance();
} catch (Exception e) {
//异常处理
}
return (T)product;
}
}
/* 客户端业务类 */
public class Client {
public static void main(String[] args) {
Creator creator = new ConcreteCreator();
Product product = creator.createProduct(ConcreteProduct1.class);
/*
* 继续业务处理
*/
}
}
工厂方法的优点
- 良好的封装性,客户端对于具体产品的构建过程毫无感知(如:创造一个人时,如何构造内脏和四肢),降低了耦合性。
- 拓展性好。增加产品时,只需要创建新的产品和新的工厂就可以,对之前的产品逻辑无需做修改。
工厂方法的本质
- 工厂方法是 new 一个对象的替代品
源码中出现工厂方法的理由
- 保证一定的封装性,降低耦合
三、抽象工厂
当一个产品在创建时,需要指定多个特点,需要在多个维度进行构建,类似于下图,则用普通的工厂模式就无法满足需求了。
3.1 书中案例
image.png对此,需要重新定义类图:
image.png
/* -------------------------------第一层人类抽象------------------------------------ */
/* 定义人类接口 */
public interface Human {
//每个人种都有相应的颜色
public void getColor();
//人类会说话
public void talk();
//每个人都有性别
public void getSex();
}
/* -------------------------------第二层人类抽象------------------------------------ */
/* 抽象白种人 */
public abstract class AbstractWhiteHuman implements Human {
//白色人种的皮肤颜色是白色的
public void getColor(){
System.out.println("白色人种的皮肤颜色是白色的! ");
}
//白色人种讲话
public void talk() {
System.out.println("白色人种会说话, 一般说的都是单字节。 ");
}
}
/* 抽象黑种人 */
public abstract class AbstractBlackHuman implements Human {
public void getColor(){
System.out.println("黑色人种的皮肤颜色是黑色的! ");
}
public void talk() {
System.out.println("黑人会说话, 一般人听不懂。 ");
}
}
/* 抽象黄种人 */
public abstract class AbstractYellowHuman implements Human {
public void getColor(){
System.out.println("黄色人种的皮肤颜色是黄色的! ");
}
public void talk() {
System.out.println("黄色人种会说话, 一般说的都是双字节。 ");
}
}
/* ----------------人类实现类(具体分男女,此处只举一个例子,黑种人和白种人为类似的代码)-------------- */
/* 黄种女人 */
public class FemaleYellowHuman extends AbstractYellowHuman {
//黄人女性
public void getSex() {
System.out.println("黄人女性");
}
}
/* 黄种男人 */
public class MaleYellowHuman extends AbstractYellowHuman {
//黄人男性
public void getSex() {
System.out.println("黄人男性");
}
}
/* -------------------------------定义工厂------------------------------------ */
/* 抽象工厂 */
public interface HumanFactory {
//制造一个黄色人种
public Human createYellowHuman();
//制造一个白色人种
public Human createWhiteHuman();
//制造一个黑色人种
public Human createBlackHuman();
}
/* 女性工厂 */
public class FemaleFactory implements HumanFactory {
//生产出黑人女性
public Human createBlackHuman() {
return new FemaleBlackHuman();
}
//生产出白人女性
public Human createWhiteHuman() {
return new FemaleWhiteHuman();
}
//生产出黄人女性
public Human createYellowHuman() {
return new FemaleYellowHuman();
}
}
/* 男性工厂 */
public class MaleFactory implements HumanFactory {
//生产出黑人男性
public Human createBlackHuman() {
return new MaleBlackHuman();
}
//生产出白人男性
public Human createWhiteHuman() {
return new MaleWhiteHuman();
}
//生产出黄人男性
public Human createYellowHuman() {
return new MaleYellowHuman();
}
}
/* 客户端业务类 */
public class NvWa {
public static void main(String[] args) {
//第一条生产线, 男性生产线
HumanFactory maleHumanFactory = new MaleFactory();
//第二条生产线, 女性生产线
HumanFactory femaleHumanFactory = new FemaleFactory();
//生产线建立完毕, 开始生产人了:
Human maleYellowHuman = maleHumanFactory.createYellowHuman();
Human femaleYellowHuman = femaleHumanFactory.createYellowHuman();
System.out.println("---生产一个黄色女性---");
femaleYellowHuman.getColor();
femaleYellowHuman.talk();
femaleYellowHuman.getSex();
System.out.println("\n---生产一个黄色男性---");
maleYellowHuman.getColor();
maleYellowHuman.talk();
maleYellowHuman.getSex();
/*
* ......
* 后面继续创建
*/
}
}
3.2 抽象工厂的定义
image.png/*----------------------------------产品A的抽象和具体定义------------------------------------*/
/* 抽象产品 */
public abstract class AbstractProductA {
//每个产品共有的方法
public void shareMethod(){}
//每个产品相同方法, 不同实现
public abstract void doSomething();
}
public class ProductA1 extends AbstractProductA {
public void doSomething() {
System.out.println("产品A1的实现方法");
}
}
public class ProductA2 extends AbstractProductA {
public void doSomething() {
System.out.println("产品A2的实现方法");
}
}
/*----------------------------------产品B的抽象和具体定义------------------------------------*/
public abstract class AbstractProductB {
//每个产品共有的方法
public void shareMethod(){}
//每个产品相同方法, 不同实现
public abstract void doSomething();
}
public class ProductB1 extends AbstractProductB {
public void doSomething() {
System.out.println("产品A1的实现方法");
}
}
public class ProductB2 extends AbstractProductB {
public void doSomething() {
System.out.println("产品A2的实现方法");
}
}
/*----------------------------------工厂的抽象和具体定义------------------------------------*/
public abstract class AbstractCreator {
//创建A产品家族
public abstract AbstractProductA createProductA();
//创建B产品家族
public abstract AbstractProductB createProductB();
}
public class Creator1 extends AbstractCreator {
//只生产产品等级为1的A产品
public AbstractProductA createProductA() {
return new ProductA1();
}
//只生产产品等级为1的B产品
public AbstractProductB createProductB() {
return new ProductB1();
}
}
public class Creator2 extends AbstractCreator {
//只生产产品等级为2的A产品
public AbstractProductA createProductA() {
return new ProductA2();
}
//只生产产品等级为2的B产品
public AbstractProductB createProductB() {
return new ProductB2();
}
}
public class Client {
public static void main(String[] args) {
//定义出两个工厂
AbstractCreator creator1 = new Creator1();
AbstractCreator creator2 = new Creator2();
//产生A1对象
AbstractProductA a1 = creator1.createProductA();
//产生A2对象
AbstractProductA a2 = creator2.createProductA();
//产生B1对象
AbstractProductB b1 = creator1.createProductB();
//产生B2对象
AbstractProductB b2 = creator2.createProductB();
/*
* 然后在这里就可以为所欲为了...
*/
}
}
注意:
以上代码,以及书中“女娲造人”的案例中,都有使用到抽象类。使用抽象类的目的,主要是为了针对实现类可以共享的方法(shareMethod()方法,以及getColor()、talk() 方法),如果没有需要复用的方法,直接用接口也可以。
四、模板方法模式
模板方法比较好理解,这里直接上定义:
4.1 定义
image.png/* 定义模板类 */
public abstract class AbstractClass {
//基本方法
protected abstract void doSomething();
//基本方法
protected abstract void doAnything();
//钩子方法
protected boolean isNeedDo() {
return true;
}
//模板方法
public void templateMethod(){
/*
* 调用基本方法, 完成相关的逻辑
*/
this.doAnything();
if (this.isNeedDo()) {
this.doSomething();
}
}
}
/* 定义子类 */
public class ConcreteClass1 extends AbstractClass {
//实现基本方法
protected void doAnything() {
//业务逻辑处理
}
protected void doSomething() {
//业务逻辑处理
}
}
public class ConcreteClass2 extends AbstractClass {//实现基本方法
protected void doAnything() {
//业务逻辑处理
}
protected void doSomething() {
//业务逻辑处理
}
protected abstract boolean isNeedDo() {
return false;
}
}
/* 客户端业务类 */
public class Client {
public static void main(String[] args) {
AbstractClass class1 = new ConcreteClass1();
AbstractClass class2 = new ConcreteClass2();
//调用模板方法
class1.templateMethod();
class2.templateMethod();
}
}
- 抽象模板中的基本方法尽量设计为protected类型, 符合迪米特法则, 不需要暴露的属性或方法尽量不要设置为protected类型。 实现类若非必要, 尽量不要扩大父类中的访问权限
- 外界条件改变, 影响到模板方法的执行。 在我们的抽象类中isNeedTo的返回值就是影响了模板方法的执行结果, 该方法就叫做钩子方法(Hook Method) 。 有了钩子方法模板方法模式才算完美
优点:
- 封装不变部分, 扩展可变部分
- 提取公共部分代码, 便于维护
- 行为由父类控制, 子类实现
网友评论