创建型设计模式
单例模式
什么是单例模式?
采取一定的方法,保证系统内某类的对象实例在系统中仅存在一个,并且该类仅提供一个取得该实例的方法。()hibernate使用的sessionFactory便使用了单例模式。
单例模式的应用场景是什么样的?举个例子
JDK中java.lang.RunTime中使用了饿汉式方式。对于经常需要创建或者销毁而且创建消耗资源大的实例对象。如:数据源,工具类对象,session工厂等。
单例模式的结构是怎么样的?
0.私有的构造方法。防止直接创建多个类实例。
1.实例在类的内部创建。
2.向外提供一个静态的公共方法。
单例模式有几种实现方式?
8种,实际上在效率和安全性上可用的有四种:内部类方式,加锁的双重检查方式,枚举类方式和饿汉式。
每种实现方式的优缺点是什么?
内部类方式通过内部静态类的延迟加载的特性,比加锁的开销更小,枚举方式不仅可以无需加锁,甚至可以防止反序列化产生新对象,但枚举没有类丰富。
什么是饿汉式和懒汉式?
在类加载阶段实例化单例的为饿汉式,运行阶段实例化的为懒汉式。
程序全局中仅存在一个全局访问的对象
普通懒加载模式:在程序运行时加载单例
优点:实现简单,在需要的时候才会创建。
缺点:多线程下辉出现不同单例,需要保证线程同步。
class LazySingleton{
private static LazySingleton lazySingleton;
private LazySingleton(){
}
public static LazySingleton getLazySingleton(){
if (lazySingleton==null){
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
原因是逃脱了空例的判断。解决方案:加synchronized同步锁。
下面是方法级别加synchronized,解决同步问题。
public synchronized static LazySingleton getLazySingleton(){
if (lazySingleton==null){
if(lazySIngleton!=null)return lazySIngleton;
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
上面这种仍存在不足:直接在方法上加synchronized,get方法是经常被调用的方法,这样每次都要获取锁,导致效率降低。
下面是代码块级别加synchronized,并且是双重检查。
private static volatile LazySingleton lazySingleton;
public static LazySingleton getLazySingleton(){
if (lazySingleton==null){
synchronized(LazySingleton.class){
if(lazySIngleton!=null)return lazySIngleton;
lazySingleton = new LazySingleton();
}
}
return lazySingleton;
}
推荐。在初次创建的时候,判断条件可能会有多个线程进入,但在synchronized代码块内部再次判断单例是否为空,保证不重复创建实例。
这个通过synchronized(LazySingleton.class)代码块,将LazySingleton作为加锁对象,因为Class是唯一的。volatile是jdk8后的修饰,当访问该修饰的变量时,会去去主存上的最新数据。
实际开发使用推荐。
内部加载模式
class Singleton{
private Singleton(){
}
//外部类被装载的时候,静态内部类并不会被装载,只有调用getInstance时才会装载,而且只会只会装载一次,不会出现线程安全问题。
private static class SingletonInner{
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance(){
return SingletonInner.INSTANCE;
}
}
**推荐。**这个方法无需通过锁的方式进行同步,利用到了静态内部类在类装载时不被装载的特性,将实例化放到静态内部类中实现,获取时通过外部方法获取内部类的字段时才进行类装载。这个过程是同步线程安全的。相比synchronized的方法更加高效。
枚举方式
public class EnumSingleton {
public static void main(String[] args) {
new Thread(()->{
EnumSingletonTest instance = EnumSingletonTest.INSTANCE;
System.out.println(instance.hashCode());
}).start();
new Thread(()->{
EnumSingletonTest instance = EnumSingletonTest.INSTANCE;
System.out.println(instance.hashCode());
}).start();
}
}
enum EnumSingletonTest{
INSTANCE;
public void say(){
System.out.println("hello");
}
}
枚举不仅可以解决多线程的问题,而且还可以防止反序列化产生新的实例对象,而且无法被反射创建,Construct类中有申明不得通过反射创建枚举实例,枚举本身也是静态且由final修饰的对象。推荐。
饿汉式
ublic class HungrySingleton {
public static void main(String[] args) {
Hungry hungry =Hungry.getInstance();
Hungry hungry2 = Hungry.getInstance();
System.out.println(hungry==hungry2);
}
}
//饿汉式单例加载
//单例模式中如何避免多线程同步问题?1.在类的加载阶段进行初始化2.
/**
* 私有构造方法,静态成员变量和获取实例方法
* 通过public获取实例
* 优点:结构简单,在类加载时期就完成了实例化,避免线程同步问题。
* 缺点:如果没用该单例会造成资源的浪费。
*/
class Hungry{
private final static Hungry hungry = new Hungry();
private Hungry(){
}
public static Hungry getInstance(){
return hungry;
}
}
饿汉式也是可用的,只是可能造成资源的浪费。
工厂模式
简单工厂模式,工厂方法模式和抽象工厂模式。
你知道的应用场景有哪些?分别用了哪种?
JDK中的Calender使用了简单工厂模式。根据获取到的caltype选择返回对象。
Calendar cal = null;
if (aLocale.hasExtensions()) {
String caltype = aLocale.getUnicodeLocaleType("ca");
if (caltype != null) {
switch (caltype) {
case "buddhist":
cal = new BuddhistCalendar(zone, aLocale);
break;
case "japanese":
cal = new JapaneseImperialCalendar(zone, aLocale);
break;
case "gregory":
cal = new GregorianCalendar(zone, aLocale);
break;
}
}
}
if (cal == null) {
if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {
cal = new BuddhistCalendar(zone, aLocale);
} else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja"
&& aLocale.getCountry() == "JP") {
cal = new JapaneseImperialCalendar(zone, aLocale);
} else {
cal = new GregorianCalendar(zone, aLocale);
}
}
return cal;
传统的继承实现类的创建有哪些弊端?
传统的继承创建,调用者往往内部随着类的种类增加,而修改内部的代码,违反了OCP原则。
工厂模式便是将调用者创建类的过程交由工厂类来实现返回,调用者直接传入参数获取对应实例便可。
简单工厂模式
简单工厂模式将散布在给出的实例化对象的过程直接封装到工厂类种,通过参数调用来返回对象。
public class SimpleFactory {
//也可以将该方法设为静态方法,无需实例化工厂
public Pizza getPizzaByName(String name){
if("BeefPizza".equals(name)){
return new BeefPizza(name);
}else if ("HamPizza".equals(name)){
return new HamPizza(name);
}else {
return null;
}
}
}
简单工厂模式对于产品增加时,也需要修改内部代码,或者增加新的简单工厂类,而这也会对调用者产生影响。灵活性并不好。
工厂方法模式
为了解决简单工厂的扩展问题,通过将需要扩展的工厂去实现共同的接口,让简单工厂模式里的各个产品分别由独立的工厂生产,让这些工厂实现同一接口。这样可以横向扩展产品。
优点:可以进行横向扩展,每个工厂的修改不影响其它工厂。
缺点:工厂类会越来越多,复杂。
抽象工厂模式
抽象工厂模式通过定义一个抽象的工厂类,其方法返回的都是产品的抽象,并且可以声明不同等级的产品,由具体工厂去实现返回产品。
比如这里的抽象工厂AbsFactory,里面定义了返回抽象Pizza的方法。然后由具体的工厂去实现,一个是专门生产北京披萨的工厂类,一个是专门生产上海披萨的工厂类,由他们去返回具体产品。
当然,还可以在抽象工厂中定义配套的其它抽象产品,如酱料,让不同工厂去实现,返回各自的实例对象。
public interface AbsFactory {
Pizza getPizzaByName(String name);
//在这可以额外添加新的不同抽象实例,让子工厂实现。
}
public class BJFactory implements AbsFactory {
@Override
public Pizza getPizzaByName(String name) {
if("BeefPizza".equals(name)){
return new BeefPizza("BJ"+name);
}else if ("HamPizza".equals(name)){
return new HamPizza("BJ"+name);
}else {
return null;
}
}
}
public class SHFactory implements AbsFactory {
@Override
public Pizza getPizzaByName(String name) {
if("BeefPizza".equals(name)){
return new BeefPizza("SH"+name);
}else if ("HamPizza".equals(name)){
return new HamPizza("SH"+name);
}else {
return null;
}
}
}
优点:抽象工厂相对其它工厂,对返回的产品进行了进一步的抽象,对调用者完全隐藏了产品的具体实现细节。
缺点:当抽象工厂实现工厂数多时,对于修改抽象工厂难度较大。
适用场景:产品拥有家族关系,互相关联,产品种类结构稳定的创建场景。
但是,如果抽象工厂内的抽象产品需要变动时,会波及其下的所有子工厂。
建造者模式
定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以有不同的表示。
作用:可以通过指挥者默认隐藏创建对象的过程细节,调用者仅需传入构造的类型和参数便可得到想要的对象,也可以将过程交由调用者自由实现。
java应用场景
StringBuilder使用了建造者模式。StringBuilder并不是具体的建造者,它只是提供了一个链式编程的类,将指挥权交给调用者,看源码可以知道它基本上所有的方法都在调用父类AbstractStringBuilder抽象类已经写好的实现。所以具体的建造者是AbstractStringBuilder。
四个角色
Product(产品角色):待构建的原始产品对象。
Builder(抽象建造者):定义原始对象每个部分操作的接口或抽象。
ConcreteBuilder(具体建造者):具体的建造者,创建Product并装配原始Product。
Director(抽象指挥者):聚合了抽象建造者,规定构建Product的装配顺序。
实例代码:
定义抽象建造者
public abstract class Builder {
abstract Builder buildA();
abstract Builder buildB();
abstract Builder buildC();
abstract Builder buildD();
abstract Product create();
}
具体的建造者实现,组合了Product
public class ConcreteBuilder extends Builder {
private Product product;
public ConcreteBuilder(){
product = new Product();
}
@Override
Builder buildA() {
product.setName("xmx");
return this;
}
@Override
Builder buildB() {
product.setAge(22);
return this;
}
@Override
Builder buildC() {
product.setSex("男");
return this;
}
@Override
Builder buildD() {
product.setLocate("江西");
return this;
}
@Override
Product create() {
return product;
}
}
指挥者,定义了构建顺序。
public class Director {
private Builder builder;
public Director(Builder builder){
this.builder = builder;
}
public Product create(){
return builder.buildA().buildB().buildC().buildD().create();
}
}
待建造的产品
public class Product {
private String name;
private int age;
private String sex;
private String locate;
}
调用者
public class Client {
public static void main(String[] args) {
Director director = new Director(new ConcreteBuilder());
Product product = director.create();
System.out.println(product.toString());
}
}
该模式往往可以与工厂模式结合使用,降低产品与建造者的耦合性。并且,如果不希望使用指挥者默认的顺序,可以去掉指挥者,将由调用者决定构造顺序。
优点:调用者无需知道对象创建的具体过程,而且可以按照抽象自定义实现新的建造者,符合OCP原则。而且可以放出建造权,让调用者自定义构建对象。可用在配置类上。
缺点:建造者模式的产品已经确定,需要创建其它产品时需要另外创建新的抽象建造者。此外,对于内部复杂的产品,往往需要优先约定默认的配置,减少内部的复杂度。