创建者模式
这类模式提供创建对象的机制, 能够提升已有代码的灵活性和可复⽤性。 创建者模式包括:⼯⼚⽅法、抽象⼯⼚、⽣成器、原型、单例,这5类。
单例模式可以说是整个设计中最简单的模式之⼀,⽽且这种⽅式即使在没有看设计模式相关资料也会常⽤在编码开发中。 因为在编程开发中经常会遇到这样⼀种场景,那就是需要保证⼀个类只有⼀个实例哪怕多线程同时访 问,并需要提供⼀个全局访问此实例的点。 综上以及我们平常的开发中,可以总结⼀条经验,单例模式主要解决的是,⼀个全局使⽤的类频繁的创建和消费,从⽽提升提升整体的代码的性能。
七种单例模式的实现
public class Singleton_00 {
public static Map<String,String> cache = new ConcurrentHashMap<String,String>();
}
以上这种⽅式在我们平常的业务开发中⾮常场常⻅,这样静态类的⽅式可以在第⼀次运⾏的时候直 接初始化Map类,同时这⾥我们也不需要到延迟加载在使⽤。 在不需要维持任何状态下,仅仅⽤于全局访问,这个使⽤使⽤静态类的⽅式更加⽅便。 但如果需要被继承以及需要维持⼀些特定状态的情况下,就适合使⽤单例模式。
spring和IOC容器单例模式就是这种实现方式。
public class Singleton_00 {
private static Singleton_01 instance;
//通过私有化构造器,别人就无法实例化此对象,只能通过提供的API
private Singleton_01(){
}
public static Singleton_01 getInstance(){
if(instance != null) return instance;
instance = new Singleton_01(); //私有方法可以内部调用
return instance;
}
}
⽬前此种⽅式的单例确实满⾜了懒加载,但是如果有多个访问者同时去获取对象实例你可以想象成 ⼀堆⼈在抢厕所,就会造成多个同样的实例并存,从⽽没有达到单例的要求。
如果需要线程安全,就可以通过在方法上添加synchronized关键字来实现线程安全,但是这样的效率还是比较低的。
public class Singleton_03 {
private static Singleton_03 instance = new Singleton_03();
private Singleton_03() {
}
public static Singleton_03 getInstance() {
return instance;
}
}
这种方式不是懒加载的,也就是类被加载这个对象就生成了。
public class Singleton_04 {
//提供静态内部类
private static class SingletonHolder {
private static Singleton_04 instance = new Singleton_04();
}
private Singleton_04() {
}
public static Singleton_04 getInstance() {
return SingletonHolder.instance;
}
}
使⽤类的静态内部类实现的单例模式,既保证了线程安全有保证了懒加载,同时不会因为加锁的⽅式耗费性能。 这主要是因为JVM虚拟机可以保证多线程并发访问的正确性,也就是⼀个类的构造⽅法在多线程环境下可以被正确的加载。此种⽅式也是⾮常推荐使⽤的⼀种单例模式
public class Singleton_05 {
private static Singleton_05 instance;
private Singleton_05() {
}
public static Singleton_05 getInstance(){
if(null != instance) return instance;
synchronized (Singleton_05.class){
if (null == instance){
instance = new Singleton_05();
}
}
return instance;
}
}
双重锁的⽅式是⽅法级锁的优化,减少了部分获取实例的耗时。 同时这种⽅式也满⾜了懒加载。这种方式只有第一次加载可能比较慢。(因为并发可能都去争夺锁资源)
public enum Singleton {
INSTANCE;
public void doSomething() {
System.out.println("doSomething");
}
}
//直接通过Singleton.INSTANCE.doSomething()的方式调用即可。方便、简洁又安全。
public class Main {
public static void main(String[] args) {
Singleton.INSTANCE.doSomething();
}
}
这也是极力推荐的方式。线程安全和单例都可以由enum的特性维护。
⼯⼚模式⼜称⼯⼚⽅法模式,是⼀种创建型设计模式,其在⽗类中提供⼀个创建对象的⽅法,允许⼦类决定实例化对象的类型。 这种设计模式也是 Java 开发中最常⻅的⼀种模式,它的主要意图是定义⼀个创建对象的接⼝,让其⼦ 类⾃⼰决定实例化哪⼀个⼯⼚类,⼯⼚模式使其创建过程延迟到⼦类进⾏。 简单说就是为了提供代码结构的扩展性,屏蔽每⼀个功能类中的具体实现逻辑。让外部可以更加简单的只是知道调⽤即可,同时,这也是去掉众多 ifelse 的⽅式。当然这可能也有⼀些缺点,⽐如需要实现的类⾮常多,如何去维护,怎样减低开发成本。但这些问题都可以在后续的设计模式结合使⽤中,逐步降低。
结构
工厂方法模式的主要角色:
举个例子
比如我们需要一个咖啡店,可以点不同的咖啡,如果不用工厂方法模式如下:
public class SimpleCoffeeFactory {
public Coffee createCoffee(String type) {
Coffee coffee = null;
if("americano".equals(type)) {
coffee = new AmericanoCoffee();
} else if("latte".equals(type)) {
coffee = new LatteCoffee();
}
return coffee;
}
}
需要啥咖啡就输入啥,确实很简单,但真的不优雅。可读性并不好,而且if else随着coffee的增多都要增多。
用工厂方法实现如下:
public interface CoffeeFactory {
Coffee createCoffee();
}
这个接口用来规范统一API
public class LatteCoffeeFactory implements CoffeeFactory {
public Coffee createCoffee() {
return new LatteCoffee();
}
}
public class AmericanCoffeeFactory implements CoffeeFactory {
public Coffee createCoffee() {
return new AmericanCoffee();
}
}
public class CoffeeStore {
private CoffeeFactory factory;
public CoffeeStore(CoffeeFactory factory) {
this.factory = factory;
}
public Coffee orderCoffee(String type) {
Coffee coffee = factory.createCoffee();
coffee.addMilk();
coffee.addsugar();
return coffee;
}
}
从以上的编写的代码可以看到,要增加产品类时也要相应地增加工厂类,不需要修改工厂类的代码了, 这样就解决了简单工厂模式的缺点。 工厂方法模式是简单工厂模式的进一步抽象。由于使用了多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点。
这样依然有缺点,因为每一种新的coffer来了,就需要实现一个新的工厂去生产。
前面介绍的工厂方法模式中考虑的是一类产品的生产,如畜牧场只养动物、电视机厂只生产电视机
这些工厂只生产同种类产品,同种类产品称为同等级产品,也就是说:工厂方法模式只考虑生产同等级的产品,但是在现实生活中许多工厂是综合型的工厂,能生产多等级(种类) 的产品,如电器厂既生产电视机又生产洗衣机或空调。
抽象工厂模式是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就 能得到同族的不同等级的产品的模式结构。 抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生 产多个等级的产品。
举个例子
大致的代码逻辑是没有改变的,感觉抽象工厂只不过多聚合了一种产品而已。
public interface DessertFactory {
Coffee createCoffee();
Dessert createDessert();
}
//美式甜点工厂
public class AmericanDessertFactory implements DessertFactory {
public Coffee createCoffee() {
return new AmericanCoffee();
}
public Dessert createDessert() {
return new MatchaMousse();
}
}
//意大利风味甜点工厂
public class ItalyDessertFactory implements DessertFactory {
public Coffee createCoffee() {
return new LatteCoffee();
}
public Dessert createDessert() {
return new Tiramisu();
}
}
如果要加同一个产品族的话,只需要再加一个对应的工厂类即可,不需要修改其他的类。
可以利用配置文件 + 工厂模式的办法达到解耦的效果:在工厂类中加载配置文件中的全 类名,并创建对象进行存储,客户端如果需要对象,直接进行获取即可。
public class CoffeeFactory {
private static Map<String,Coffee> cache = new HashMap<String, Coffee>();
static {
Properties properties = new Properties();
InputStream stream = CoffeeFactory.class.getClassLoader().getResourceAsStream("bean.properties");
try {
properties.load(stream);
Set<Object> key = properties.keySet();
for (Object o : key) {
String className = properties.getProperty((String) o);
//反射创建对象
Class<?> aClass = Class.forName(className);
Coffee coffee = (Coffee) aClass.newInstance();
cache.put((String) o,coffee);
}
} catch (Exception e) {
}
}
public static Coffee getCoffee(String name){
return cache.get(name);
}
}
//文件内容如下:
american=sheep.Singleton.AmericanCoffee
latte=sheep.Singleton.LatteCoffee
//测试
public void test1(){
Coffee latte = CoffeeFactory.getCoffee("latte");
System.out.println(latte);
}
将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示。
建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对 象的类型就可以得到该对象,而无须知道其内部的具体构造细节。
结构如下
建造者(Builder)模式包含如下角色:
举个例子
生产自行车是一个复杂的过程,它包含了车架,车座等组件的生产。而车架又有碳纤维,铝合金等材质 的,车座有橡胶,真皮等材质。对于自行车的生产就可以使用建造者模式。 这里Bike是产品,包含车架,车座等组件;Builder是抽象建造者,MobikeBuilder和 OfoBuilder是具体的建造者;Director是指挥者。
//自行车类
public class Bike {
private String frame;
private String seat;
public String getFrame() {
return frame;
}
public void setFrame(String frame) {
this.frame = frame;
}
public String getSeat() {
return seat;
}
public void setSeat(String seat) {
this.seat = seat;
}
}
public abstract class Builder {
protected Bike mBike = new Bike();
public abstract void buildFrame();
public abstract void buildSeat();
public abstract Bike createBike();
}
public class OfoBuilder extends Builder {
Bike mBike = new Bike();
public void buildFrame() {
mBike.setFrame("碳钎维车架");
}
public void buildSeat() {
mBike.setSeat("橡胶");
}
public Bike createBike() {
return mBike;
}
}
public class Director {
private Builder mBuilder;
public Director(Builder builder) {
mBuilder = builder;
}
public Bike construct() {
mBuilder.buildFrame();
mBuilder.buildSeat();
return mBuilder.createBike();
}
}
这个时候,我们需要啥,只需要告诉指挥者,他就可以帮我们搞定。Builder好比是各种组件,不同的东西有不同的类型组件,而Director是怎么组装这些组件。
用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。
原型模式的克隆分为浅克隆和深克隆。
Java中的Object类中提供了 clone() 方法来实现浅克隆。 Cloneable 接口是抽象原型类,而实现了Cloneable接口的子实现类就是具体的原型类。
举个例子
同一学校的“三好学生”奖状除了获奖人姓名不同,其他都相同,可以使用原型模式复制多个“三好学 生”奖状出来,然后在修改奖状上的名字即可。
//奖状类
public class Citation implements Cloneable {
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return (this.name);
}
public void show() {
System.out.println(name + "同学:表现优秀,被评为三好学生。特发此状!");
}
@Override
public Citation clone() throws CloneNotSupportedException {
return (Citation) super.clone();
}
}
//测试访问类
public class CitationTest {
public static void main(String[] args) throws
CloneNotSupportedException {
Citation c1 = new Citation();
c1.setName("张三");
//复制奖状
Citation c2 = c1.clone();
//将奖状的名字修改李四
c2.setName("李四");
c1.show();
c2.show();
}
}
这种方式并不好,学生因该是个单独的类,奖状聚合学生:
public class Citation implements Cloneable {
private Student stu;
public Student getStu() {
return stu;
}
public void setStu(Student stu) {
this.stu = stu;
}
public void show() {
System.out.println(stu.getName + "同学:表现优秀,被评为三好学生。特发此状!");
}
@Override
public Citation clone() throws CloneNotSupportedException {
return (Citation) super.clone();
}
}
//测试类
public class CitationTest {
public static void main(String[] args) throws
CloneNotSupportedException {
Citation c1 = new Citation();
Student stu = new Student("张三", "西安");
c1.setStu(stu);
//复制奖状
Citation c2 = c1.clone();
//获取c2奖状所属学生对象
Student stu1 = c2.getStu();
stu1.setName("李四");
//判断stu对象和stu1对象是否是同一个对象
System.out.println("stu和stu1是同一个对象?" + (stu == stu1));
c1.show();
c2.show();
}
}
这样改动之后测试,发现两次都是李四,这就是浅拷贝,因为学生是引用类型,所以拷贝的时候把地址拷贝过来,而操作地址就会引起原数据的改变。
想要显示不同的人,就需要深度拷贝,也就是学生不再拷贝地址,而是拷贝整个实例。
这个时候就可以使用序列化的方式,把引用的对象序列化再反序列化,这样就可以得到一个一样的但是确确实实是两个对象。
public class CitationTest1 {
public static void main(String[] args) throws Exception {
Citation c1 = new Citation();
Student stu = new Student("张三", "西安");
c1.setStu(stu);
//创建对象输出流对象
ObjectOutputStream oos = new ObjectOutputStream(new
FileOutputStream("C:\\Users\\Think\\Desktop\\b.txt"));
//将c1对象写出到文件中
oos.writeObject(c1);
oos.close();
//创建对象出入流对象
ObjectInputStream ois = new ObjectInputStream(new
FileInputStream("C:\\Users\\Think\\Desktop\\b.txt"));
//读取对象
Citation c2 = (Citation) ois.readObject();
//获取c2奖状所属学生对象
Student stu1 = c2.getStu();
stu1.setName("李四");
//判断stu对象和stu1对象是否是同一个对象
System.out.println("stu和stu1是同一个对象?" + (stu == stu1));
}
}