设计模式-创建型(学习归纳)

创建型设计模式主要有4种:单例模式、工厂模式、建造者模式、原型模式

单例模式

主要分为两种:懒汉式(当第一次调用时进行创建)、饿汉式(在系统加载时进行创建)
主要的好处:1.减少系统开销 2.减轻GC压力
注意的点:单例类必须有一个private访问级别的构造函数 成员变量与方法必须是static的

常见的懒汉式:

1.非线程安全和synchronized关键字线程安全版本

package testDesignPattern.single;

public class BasicLazy {
	// 在第一次调用时创建,但是并不能保证线程安全
	private static BasicLazy basicLazy;
	private BasicLazy(){}
	public static BasicLazy getBasicLazy(){
		if (basicLazy == null){
			// 当两个线程都进入到这边,会导致创建两次。 可以在这个get方法前面增加synchronized 来保证线程安全
			basicLazy = new BasicLazy();
		}
		return basicLazy;
	}
}

2.双重检查加锁版本

package testDesignPattern.single;

public class DoubleCheckedLazy {
	// 利用synchronized 来保证doubleCheckedLazy只被创建一次,
	// 并且利用volatile保证doubleCheckedLazy的数据可见性
	private static volatile DoubleCheckedLazy doubleCheckedLazy;
	private DoubleCheckedLazy(){}

	public DoubleCheckedLazy getDoubleCheckedLazy(){
		if (doubleCheckedLazy == null){
			synchronized (DoubleCheckedLazy.class){
				doubleCheckedLazy = new DoubleCheckedLazy();
			}
		}
		return doubleCheckedLazy;
	}
}

3.登记式/静态内部类方式

package testDesignPattern.single;

public class RegisterLazy {
	// 只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类
	// 从而实例化 instance(只有第一次使用这个单例的实例的时候才加载,同时不会有线程安全问题)。
	private static class RegisterLazyHolder{
		private static final RegisterLazy INSTANCE = new RegisterLazy();
	}
	private RegisterLazy(){}

	public static RegisterLazy getRegisterLazy(){
		return RegisterLazyHolder.INSTANCE;
	}
}

懒汉式中,比较常用且推荐的是第二种双重检查锁版本。

常见的饿汉式:

1.基本饿汉式

package testDesignPattern.single;

public class BasicHunger {
	// 在JVM加载时,这个类就被创建出来,所以保证了线程安全
	private static BasicHunger basicHunger = new BasicHunger();
	private BasicHunger(){ }

	public static BasicHunger getBasicHunger(){
		return basicHunger;
	}
}

2.枚举方式

package testDesignPattern.single;

public enum  EnumHunger {
	// 这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。
	// 它更简洁,自动支持序列化机制,绝对防止多次实例化
	// 同时这种方式也是《Effective Java 》以及《Java与模式》的作者推荐的方式。
	INSTANCE;
	public void doSomeThing(){
		System.out.println("do something");
	}
}

调用方式:

	public static void main(String[] args) throws InterruptedException {
		EnumHunger a = EnumHunger.INSTANCE;
		a.doSomeThing();
	}

小结

单例模式推荐的是饿汉的第二种枚举模式(这种方法在功能上与公有域方法相近,但是它更加简洁,无偿提供了序列化机制,绝对防止多次实例化,即使是在面对复杂序列化或者反射攻击的时候。虽然这种方法还没有广泛采用,但是单元素的枚举类型已经成为实现Singleton的最佳方法。 —-《Effective Java 中文版 第二版》)
单例模式在日常工作中十分常见,如日志中的log4j、缓存、注册表、打印机、等等。事实上,这一类对象只能有一个实例,如果制造出多个实例就可能会导致一些问题的产生。

工厂模式

工厂模式主要有三种:简单工厂、工厂方法、抽象工厂
好处:解耦(对象的创建和使用的过程分开)、降低代码重复、降低维护成本(开闭原则)

简单工厂

接口以及实现类(这个不是很关键所以代码放一起了)

public interface Shape {
	void draw();
}
public class Circle implements Shape{
	public Circle() {
		System.out.println("Circle");
	}

	@Override
	public void draw() {
		System.out.println("Draw Circle");
	}
}
public class Rectangle implements Shape {
	public Rectangle(){
		System.out.println("Rectangle");
	}
	@Override
	public void draw() {
		System.out.println("Draw Rectangle");
	}
}
public class Square implements Shape {
	public Square(){
		System.out.println("Square");
	}
	@Override
	public void draw() {
		System.out.println("Draw Square");
	}
}

工厂类:

package testDesignPattern.factory.simpleFactory;

public class ShapeFactory {
	public static Shape getShape(String shapeType){
		if (shapeType==null){
			return null;
		}
		if (shapeType.equalsIgnoreCase("CIRCLE")) {
			return new Circle();
		} else if (shapeType.equalsIgnoreCase("RECTANGLE")) {
			return new Rectangle();
		} else if (shapeType.equalsIgnoreCase("SQUARE")) {
			return new Square();
		}
		return null;
	}
}

调用方式可以使用:

		Shape circle = ShapeFactory.getShape("circle");
		circle.draw();
		Shape rectangle = ShapeFactory.getShape("rectangle");
		rectangle.draw();
		Shape square = ShapeFactory.getShape("square");
		square.draw();

但是其实这并不满足开闭原则,当需要新加一个类,如三角形,那么也需要改动ShapeFactory。这里可以使用反射的机制,简单改动一下工厂类。

package testDesignPattern.factory.simpleFactory;

public class ShapeFactoryReflex {
	public static Object getClass(Class<? extends Shape> clazz){
		Object obj = null;
		try {
			obj = Class.forName(clazz.getName()).newInstance();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		return obj;
	}
}

调用方式:

		Circle circleReflex = (Circle) ShapeFactoryReflex.getClass(Circle.class);
		circleReflex.draw();
		Rectangle rectangleReflex = (Rectangle) ShapeFactoryReflex.getClass(Rectangle.class);
		rectangleReflex.draw();
		Square squareReflex = (Square) ShapeFactoryReflex.getClass(Square.class);
		squareReflex.draw();

这样修改后,新增一个实现类时,也无需改动工厂类了。

工厂方法

一个类通过其子类来指定创建哪个对象:在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏
将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无需关心是哪一个工厂子类创建产品子类,需要时再动态指定,可将具体工厂类的类名存储在配置文件或数据库中。
具体实现:
公共的工厂接口:

package testDesignPattern.factory.factoryMethod;
import testDesignPattern.factory.simpleFactory.Shape;

public interface Factory {
	public Shape getShape();
}

添加相关工厂类:

package testDesignPattern.factory.factoryMethod;

import testDesignPattern.factory.simpleFactory.Circle;
import testDesignPattern.factory.simpleFactory.Shape;

public class CircleFactory implements Factory {
	@Override
	public Shape getShape() {
		return new Circle();
	}
}

其他的相关类也是类似。
测试类:

package testDesignPattern.factory.factoryMethod;

import testDesignPattern.factory.simpleFactory.Circle;
import testDesignPattern.factory.simpleFactory.Shape;

public class TestFactoryMethod {
	public static void main(String[] args) {
		Factory circleFactory = new CircleFactory();
		Shape circle = circleFactory.getShape();
		circle.draw();
	}
}

抽象工厂

抽象工厂总体与工厂方法有点类似,区别在于抽象工厂是生产一整套有产品的(至少要生产两个产品),这些产品必须相互是有关系或有依赖的,而工厂方法中的工厂是生产单一产品的工厂。

创建相关接口:

package testDesignPattern.factory.abstractFactory;

public interface Gun {
	public void shooting();
}
package testDesignPattern.factory.abstractFactory;

public interface Bullet {
	public void load();
}

创建接口实现类:

package testDesignPattern.factory.abstractFactory;

public class AK implements Gun {
	@Override
	public void shooting() {
		System.out.println("shooting with AK");
	}
}
package testDesignPattern.factory.abstractFactory;

public class AK_Bullet implements Bullet {
	@Override
	public void load() {
		System.out.println("Load bullet with AK");
	}
}

创建工厂接口:

package testDesignPattern.factory.abstractFactory;

public interface Factory {
	public Gun produceGun();
	public Bullet produceBullet();
}

创建具体工厂:

package testDesignPattern.factory.abstractFactory;

public class AK_Factory implements Factory {
	@Override
	public Gun produceGun() {
		return new AK();
	}

	@Override
	public Bullet produceBullet() {
		return new AK_Bullet();
	}
}

测试类:

package testDesignPattern.factory.abstractFactory;

public class TestAbstractFactory {
	public static void main(String[] args) {
		Factory factory;
		Gun gun;
		Bullet bullet;

		factory =new AK_Factory();
		bullet=factory.produceBullet();
		bullet.load();
		gun=factory.produceGun();
		gun.shooting();
	}
}

小结:在开源框架中存在许多工厂类,常见的spring中的getBean(),BeanFactory等等。个人认为工厂模式的主要作用就是解耦,将类的创建与使用隔离开,使用者无需知道类是如何创建的,安心使用即可。
GOF为工厂模式的定义:在基类中定义创建对象的一个接口,让子类决定实例化哪个类。工厂方法让一个类的实例化延迟到子类中进行。

建造者模式

总体包含四个角色:
Product(产品角色):一个具体的产品对象。
Builder(抽象建造者):创建一个Product对象的各个部件指定的抽象接口。
ConcreteBuilder(具体建造者):实现抽象接口,构建和装配各个部件。
Director(指挥者):构建一个使用Builder接口的对象。它主要是用于创建一个复杂的对象。它主要有两个作用,一是:隔离了客户与对象的生产过程,二是:负责控制产品对象的生产过程。

为什么要用建造者模式(优点)?
1) 客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
2) 每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者, 用户使用不同的具体建造者即可得到不同的产品对象 。
3) 可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
4) 增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程,系统扩展方便,符合 “开闭原则”
哪些情况不要用建造者模式(缺点)?
1) 产品之间差异性很大的情况:建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
2) 产品内部变化很复杂的情况: 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。
下面使用一个kfc套餐的例子:
首先建立一个产品对象(套餐框架)

package testDesignPattern.builder;

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;
	}
	@Override
	public String toString() {
		return "Meal{" +
				"food='" + food + '\'' +
				", drink='" + drink + '\'' +
				'}';
	}
}

之后建立一个抽象建造者

package testDesignPattern.builder;

public abstract class MealBuilder {
	Meal meal = new Meal();
	public abstract void buildFood();
	public abstract void buildDrink();
	public Meal getMeal(){
		return meal;
	}
}

建立具体建造者(这里就是具体的套餐)(实现抽象接口,构建和装配各个部件。)

package testDesignPattern.builder;

public class MealA extends MealBuilder {

	@Override
	public void buildFood() {
		meal.setFood("薯条");
	}

	@Override
	public void buildDrink() {
		meal.setDrink("可乐");
	}
}
package testDesignPattern.builder;

public class MealB extends MealBuilder {
	@Override
	public void buildFood() {
		meal.setFood("鸡翅");
	}

	@Override
	public void buildDrink() {
		meal.setDrink("雪碧");
	}
}

最后建立指挥者(用户直接面向的–这里是服务员)

package testDesignPattern.builder;

public class KFCWaiter {
	private MealBuilder mealBuilder;
	public KFCWaiter(MealBuilder mealBuilder){
		this.mealBuilder=mealBuilder;
	}
	public Meal construct(){
		//准备食物
		mealBuilder.buildFood();
		//准备饮料
		mealBuilder.buildDrink();
		//准备完毕,返回一个完整的套餐给客户
		return mealBuilder.getMeal();
	}
}

具体调用方式:

package testDesignPattern.builder;

public class TestBuilder {
	public static void main(String[] args) {
		MealA a = new MealA();
		KFCWaiter waiter = new KFCWaiter(a);
		Meal mealA = waiter.construct();
		System.out.println(mealA);
	}
	
}

小结

建造者模式 是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。

原型模式

1.原型模式的优点:
当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过一个已有实例可以提高新实例的创建效率。
可以动态增加或减少产品类。
原型模式提供了简化的创建结构。
可以使用深克隆的方式保存对象的状态。
2.原型模式的缺点:
需要为每一个类配备一个克隆方法,而且这个克隆方法需要对类的功能进行通盘考虑,这对全新的类来说不是很难,但对已有的类进行改造时,不一定是件容易的事,必须修改其源代码,违背了“开闭原则”。
在实现深克隆时需要编写较为复杂的代码。
例子:
Prototype(抽象原型类)
负责定义用于复制现有实例来生成新实例的方法。

package testDesignPattern.archetypal;

public interface Product extends Cloneable {
	//use方法时用于"使用"的方法,具体怎么"使用",则交给子类去实现
	public abstract void use(String s);
	//createClone方法是用于复制实例的方法
	public abstract Product creatClone();
}

ConcretePrototype(具体原型类)
ConcretePrototype角色负责实现复制现有实例并生成新实例的方法

package testDesignPattern.archetypal;

public class MessageBox implements Product {
	//保存的是装饰方框使用的字符样式
	private 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 creatClone() {
		Product p = null;
		try {
			p=(Product) clone();
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return p;
	}
}
package testDesignPattern.archetypal;

public class UnderlinePen implements Product {
	private char ulchar;

	public UnderlinePen(char ulchar){
		this.ulchar = ulchar;
	}
	@Override
	public void use(String s) {
		int length = s.getBytes().length;
		System.out.println("\""+s+"\"");
		for (int i = 0; i < length + 2; i++) {
			System.out.print(ulchar);
		}
		System.out.println("");
	}

	@Override
	public Product creatClone() {
		Product p = null;
		try {
			p=(Product) clone();
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return p;
	}
}

Client(客户端/使用者)
Client角色负责使用复制实例的方法生成新的实例

package testDesignPattern.archetypal;

import java.util.HashMap;

public class Manager {
	//保存实例的"名字"和"实例"之间的对应关系
	private HashMap showcase = new HashMap<>();
	//register方法将接受到的一组"名字"和"Product接口"注册到showcase中。
	//这里Product是实现Product接口的实例,具体还未确定
	public void register(String name,Product product){
		showcase.put(name,product);
	}
	public Product create(String productName){
		Product p = showcase.get(productName);
		return p.creatClone();
	}
}

调用方式:

package testDesignPattern.archetypal;

public class TestArchetypal {
	public static void main(String[] args) {
		Manager manager = new Manager();
		UnderlinePen underlinePen = new UnderlinePen('~');
		MessageBox mbox = new MessageBox('*');
		MessageBox sbox = new MessageBox('/');
		manager.register("Strong message",underlinePen);
		manager.register("Waring Box",mbox);
		manager.register("Slash Box",sbox);
		Product p1 = manager.create("Strong message");
		p1.use("hello world");
		Product p2 = manager.create("Waring Box");
		p2.use("hello world");
		Product p3 = manager.create("Slash Box");
		p3.use("Slash Box");
	}
}

小结

(1) 原型模式应用于很多软件中,如果每次创建一个对象要花大量时间,原型模式是最好的解决方案。很多软件提供的复制(Ctrl + C)和粘贴(Ctrl + V)操作就是原型模式的应用,复制得到的对象与原型对象是两个类型相同但内存地址不同的对象,通过原型模式可以大大提高对象的创建效率。
(2) 在Struts2中为了保证线程的安全性,Action对象的创建使用了原型模式,访问一个已经存在的`Action对象时将通过克隆的方式创建出一个新的对象,从而保证其中定义的变量无须进行加锁实现同步,每一个Action中都有自己的成员变量,避免Struts1因使用单例模式而导致的并发和同步问题。
(3) 在Spring中,用户也可以采用原型模式来创建新的bean实例,从而实现每次获取的是通过克隆生成的新实例,对其进行修改时对原有实例对象不造成任何影响。
GOF给出的原型模式定义如下:
Specify the kind of objects to create using a prototypical instance, and create new objects by copying this prototype. (使用原型实例指定将要创建的对象类型,通过复制这个实例创建新的对象。)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值