带你读懂几种常见的设计模式 第三弹
本文已经是设计模式系列的第三篇文章了,今天来讲讲单例模式、抽象工厂模式和适配器模式。
1、单例模式
单例模式让一个类最多只有一个实例。具体的做法是:
- 让类的构造方法私有化
- 提供一个用于获取该类实例的静态公共方法给别的类访问
- 如果是在多线程的环境下,还要考虑线程安全性,防止产生多个实例
单例模式有多种实现方式:
- 饿汉式
- 懒汉式
- 双重检查锁
- 静态内部类
1.1、饿汉式
饿汉式的优点就是简单,不会发生线程安全问题。缺点就是实例对象在类加载时期就new出来了,会损耗一定的空间。
public class EhanShi {
    private static EhanShi ehanShi = new EhanShi();
    private EhanShi(){
    }
    public static EhanShi getInstance(){
        return ehanShi;
    }
}1.2、懒汉式
懒汉式的优点是懒加载,不会提前损耗空间。缺点是需要加锁来保证线程安全性,因此对性能影响特别大。
public class LanhanShi {
    private static LanhanShi lanhanShi = null;
    private LanhanShi(){
    }
    public synchronized static LanhanShi getInstance(){
        if(lanhanShi == null){
            lanhanShi = new LanhanShi();
        }
        return lanhanShi;
    }
}1.3、双重检查锁
双重检查锁(DCL),虽然加锁了,但是对性能影响不大。
注意,属性必须加volatile,防止指令重排序和保证可见性,虽然牺牲一点性能,但是是值得的。
虽然这种方式既保证了线程安全性、又不会提前损耗空间、对性能影响也不大,但是在多线程的情况下仍然会有一些问题,不建议用。
public class DoubleCheckLock {
    private volatile static DoubleCheckLock doubleCheckLock = null;
    private DoubleCheckLock(){
    }
    public static DoubleCheckLock getInstance(){
        if(doubleCheckLock == null){
            synchronized (DoubleCheckLock.class){
                if(doubleCheckLock == null){
                    doubleCheckLock = new DoubleCheckLock();
                }
            }
        }
        return doubleCheckLock;
    }
}1.4、静态内部类(推荐)
静态内部类的方式跟饿汉式的区别就是:饿汉式在第一次类加载的时候就初始化好了实例对象,而静态内部类的方式第一次类加载的时候不会初始化实例,因为实例对象是在静态内部类里的。
这其实是利用了类加载器的线程安全性,ClassLoader类的loadClass方法加了synchronized关键字。
public class StaticInnerClass {
    private StaticInnerClass(){
    }
    public static StaticInnerClass getInstance(){
        return StaticInnerClassHolder.staticInnerClass;
    }
    private static class StaticInnerClassHolder{
        private static final StaticInnerClass staticInnerClass = new StaticInnerClass();
    }
}2、适配器模式
适配器模式在生活中也很常见,比如插座。
现在墙上只有一个三孔插头,而手机充电机是两孔的,这时候插座就充当适配器的角色。
还有一个例子,路人甲会说英文不会说中文,路人乙会说中文不会说英文,如果他俩想交流的话,就需要一个翻译适配器,而该适配器就负责将中文翻译为英文,将英文翻译为中文。
下面以翻译适配器为例,直接上代码。
Speak接口提供speak方法,word参数为要说的话,languageType参数表示这句话是中文还是英文。
public interface Speak {
    void speak(String word,String languageType);
}下面分别定义说中文类和说英文类。
public class CanSpeakChinese implements Speak {
    @Override
    public void speak(String word, String languageType) {
        if("中文".equals(languageType)){
            System.out.println(word);
        }else{
            System.out.println("我不懂这是什么语言");
        }
    }
}
public class CanSpeakEnglish implements Speak {
    @Override
    public void speak(String word,String languageType) {
        if("英文".equals(languageType)){
            System.out.println(word);
        }else{
            System.out.println("我不懂这是什么语言");
        }
    }
}然后再定义一个翻译适配器类。
public class TranslateAdapter implements Speak {
    private CanSpeakChinese canSpeakChinese = new CanSpeakChinese();
    private CanSpeakEnglish canSpeakEnglish = new CanSpeakEnglish();
    @Override
    public void speak(String word, String languageType) {
        if("中文".equals(languageType)){
            canSpeakEnglish.speak(translate(word,"将中文翻译为英文"),"英文");
        }else if("英文".equals(languageType)){
            canSpeakChinese.speak(translate(word,"将英文翻译为中文"),"中文");
        }
    }
    /**
     * 模拟翻译
     */
    public String translate(String word,String type){
        if("将中文翻译为英文".equals(type)){
            return "这是英文["+word+"]";
        }else if("将英文翻译为中文".equals(type)){
            return "这是中文["+word+"]";
        }else{
            return word;
        }
    }
}最后定义main方法运行测试。
public class AdapterTest {
    public static void main(String[] args) {
        CanSpeakChinese speakChinese = new CanSpeakChinese();
        CanSpeakEnglish speakEnglish = new CanSpeakEnglish();
        TranslateAdapter adapter = new TranslateAdapter();
        // 说中文
        speakChinese.speak("你好","中文");
        speakChinese.speak("hello","英文");
        //说英文
        speakChinese.speak("nice to meet you","英文");
        speakChinese.speak("很高兴遇见你","中文");
        //翻译适配器
        adapter.speak("how are you?","英文");
        adapter.speak("你最近好吗","中文");
    }
}输出结果如下。
你好
我不懂这是什么语言
我不懂这是什么语言
很高兴遇见你
这是中文[how are you?]
这是英文[你最近好吗]适配器模式比较简单,我就不解释代码了。
3、抽象工厂模式
前面的两篇文章介绍过简单工厂和工厂方法模式,接下来要讲的抽象工厂是另一种工厂模式了。
关于简单工厂模式和工厂方法模式请分别戳下面的链接:
设计模式之简单工厂、策略模式
设计模式之装饰器、代理、工厂方法模式
工厂方法模式中的工厂,只能生产动物,也只能叫动物工厂。如果想生产别的东西怎么办呢,比如车?这时候我们可以把工厂再做一层抽象。
下面就用代码来演示抽象工厂模式。使用的案例还是以Animal作为父接口,但是为了方便其子类不再是Cat、Dog、Pig。
定义Animal家族。
public interface Animal {
    void sayHello();
}
public class BeijingAnimal implements Animal{
    @Override
    public void sayHello() {
        System.out.println("北京的动物 say hello!");
    }
}
public class ShanghaiAnimal implements Animal{
    @Override
    public void sayHello() {
        System.out.println("上海的动物 say hello!");
    }
}定义Car家族。
public interface Car {
    void block();
}
public class BeijingCar implements Car{
    @Override
    public void block() {
        System.out.println("北京的车 堵车了");
    }
}
public class ShanghaiCar implements Car{
    @Override
    public void block() {
        System.out.println("上海的车 堵车了");
    }
}定义抽象工厂。
public interface CityFactory {
    Animal getAnimal();
    Car getCar();
}定义实际工厂BeijingFactory、ShanghaiFactory,前者只能生产北京的动物和车,后者只能生产上海的动物和车。
public class BeijingFactory implements CityFactory {
    @Override
    public Animal getAnimal() {
        return new BeijingAnimal();
    }
    @Override
    public Car getCar() {
        return new BeijingCar();
    }
}
public class ShanghaiFactory implements CityFactory {
    @Override
    public Animal getAnimal() {
        return new ShanghaiAnimal();
    }
    @Override
    public Car getCar() {
        return new ShanghaiCar();
    }
}定义main方法运行测试。
public class ShanghaiFactory implements CityFactory {
    @Override
    public Animal getAnimal() {
        return new ShanghaiAnimal();
    }
    @Override
    public Car getCar() {
        return new ShanghaiCar();
    }
}输出结果如下。
北京的动物 say hello!
北京的车 堵车了
上海的动物 say hello!
上海的车 堵车了下面,再看一个例子,关于实际项目中多数据源切换的问题。
3.1、抽象工厂模式实践-多数据源
现在,假设有user和role两张表,然后有两种数据源分别是mysql和oracle,这时候可以利用抽象工厂来封装多数据源。
模式真实项目分别定义User、Role实体类。
public class User {
    private int id;
    private String username;
    public User(int id, String username) {
        this.id = id;
        this.username = username;
    }
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                '}';
    }
}
public class Role {
    private int id;
    private String rolename;
    public Role(int id, String rolename) {
        this.id = id;
        this.rolename = rolename;
    }
    @Override
    public String toString() {
        return "Role{" +
                "id=" + id +
                ", rolename='" + rolename + '\'' +
                '}';
    }
}定义抽象数据访问接口UserDao、RoleDao。
public interface UserDao {
    void insert(User user);
    User getUser(int id);
}
public interface RoleDao {
    void insert(Role role);
    Role getRole(int id);
}定义抽象数据访问接口的mysql实现。
public class MysqlUserDao implements UserDao{
    @Override
    public void insert(User user) {
        System.out.println("mysql数据库插入了一条User:"+user);
    }
    @Override
    public User getUser(int id) {
        System.out.println("mysql数据库查询到了id为"+id+"的User");
        return null;
    }
}
public class MysqlRoleDao implements RoleDao{
    @Override
    public void insert(Role role) {
        System.out.println("mysql数据库插入了一条Role:"+role);
    }
    @Override
    public Role getRole(int id) {
        System.out.println("mysql数据库查询到了id为"+id+"的Role");
        return null;
    }
}定义抽象数据访问接口的oracle实现。
public class OracleUserDao implements UserDao{
    @Override
    public void insert(User user) {
        System.out.println("oracle数据库插入了一条User:"+user);
    }
    @Override
    public User getUser(int id) {
        System.out.println("oracle数据库查询到了id为"+id+"User");
        return null;
    }
}
public class OracleRoleDao implements RoleDao{
    @Override
    public void insert(Role role) {
        System.out.println("oracle数据库插入了一条Role:"+role);
    }
    @Override
    public Role getRole(int id) {
        System.out.println("oracle数据库查询到了id为"+id+"的Role");
        return null;
    }
}定义抽象工厂。
public interface DBFactory {
    UserDao getUserDao();
    RoleDao getRoleDao();
}定义抽象工厂的mysql实现和oracle实现。
public class MysqlDBFactory implements DBFactory{
    @Override
    public UserDao getUserDao() {
        return new MysqlUserDao();
    }
    @Override
    public RoleDao getRoleDao() {
        return new MysqlRoleDao();
    }
}
public class OracleDBFactory implements DBFactory{
    @Override
    public UserDao getUserDao() {
        return new OracleUserDao();
    }
    @Override
    public RoleDao getRoleDao() {
        return new OracleRoleDao();
    }
}定义main方法运行测试。
public class DBFactoryTest {
    public static void main(String[] args) {
        MysqlDBFactory mysqlDBFactory = new MysqlDBFactory();
        UserDao muserDao = mysqlDBFactory.getUserDao();
        RoleDao mroleDao = mysqlDBFactory.getRoleDao();
        muserDao.insert(new User(15,"张三"));
        muserDao.getUser(20);
        mroleDao.insert(new Role(3,"普通管理员"));
        mroleDao.getRole(6);
        OracleDBFactory oracleDBFactory = new OracleDBFactory();
        UserDao ouserDao = oracleDBFactory.getUserDao();
        RoleDao oroleDao = oracleDBFactory.getRoleDao();
        ouserDao.insert(new User(18,"李四"));
        ouserDao.getUser(12);
        oroleDao.insert(new Role(2,"超级管理员"));
        oroleDao.getRole(8);
    }
}输出结果如下。
mysql数据库插入了一条User:User{id=15, username='张三'}
mysql数据库查询到了id为20的User
mysql数据库插入了一条Role:Role{id=3, rolename='普通管理员'}
mysql数据库查询到了id为6的Role
oracle数据库插入了一条User:User{id=18, username='李四'}
oracle数据库查询到了id为12User
oracle数据库插入了一条Role:Role{id=2, rolename='超级管理员'}
oracle数据库查询到了id为8的Role感觉抽象工厂实现起来是不是有点复杂了,这时候我们可以借助简单工厂模式,用一个集中式的工厂来代替MysqlDBFactory和OracleDBFactory多个工厂。
3.2、用简单工厂改进抽象工厂
定义集中式的数据访问接口。
默认的数据源是mysql,通过构造方法切换为其它数据源。
public class DBAccess implements DBFactory{
    private String db = "mysql";
    public DBAccess() {
    }
    public DBAccess(String db) {
        this.db = db;
    }
    @Override
    public UserDao getUserDao() {
        switch (db){
            case "mysql":
                return new MysqlUserDao();
            case "oracle":
                return new OracleUserDao();
            default:
                return new MysqlUserDao();
        }
    }
    @Override
    public RoleDao getRoleDao() {
        switch (db){
            case "mysql":
                return new MysqlRoleDao();
            case "oracle":
                return new OracleRoleDao();
            default:
                return new MysqlRoleDao();
        }
    }
}定义main方法运行测试。
public class DBAccessTest {
    public static void main(String[] args) {
        DBAccess access = new DBAccess("oracle");
        UserDao userDao = access.getUserDao();
        userDao.insert(new User(5,"小明"));
    }
}输出结果如下。
oracle数据库插入了一条User:User{id=5, username='小明'}但是简单工厂又不符合开闭原则,我们可以利用反射技术,让具体的工厂类是动态生成的,而不是在switch中写死。
3.3、用反射改进抽象工厂
利用反射技术来增强集中式数据访问接口DBAccess。
public class DBAccessByReflect implements DBFactory{
    private static final String PACKAGE_NAME = "com.bobo.group.abstractfactory.db.";
    private UserDao userDao = new MysqlUserDao();
    private RoleDao roleDao = new MysqlRoleDao();
    public DBAccessByReflect() {
    }
    public DBAccessByReflect(String db) {
        String userDaoclassName = PACKAGE_NAME+db+"UserDao";
        String roleDaoclassName = PACKAGE_NAME+db+"RoleDao";
        try {
            userDao = (UserDao)Class.forName(userDaoclassName).newInstance();
            roleDao = (RoleDao)Class.forName(roleDaoclassName).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @Override
    public UserDao getUserDao() {
        return userDao;
    }
    @Override
    public RoleDao getRoleDao() {
        return roleDao;
    }
}定义main方法运行测试。
public class DBAccessByReflectTest{
    public static void main(String[] args) {
        DBAccessByReflect access = new DBAccessByReflect("Oracle");
        UserDao userDao = access.getUserDao();
        userDao.insert(new User(1,"小红"));
    }
}输出结果如下。
oracle数据库插入了一条User:User{id=1, username='小红'}这样是不是好多了?反射技术是真滴强!
  
今天的文章一共讲了三个设计模式,字比较少,代码比较多,建议把代码copy到自己的IDE中跑一遍,这样印象才会更深呀!










