设计模式之单例模式、适配器模式、抽象工厂模式

引言

原文链接:带你读懂几种常见的设计模式 第三弹 希望点进去的小伙伴关注一下我的公众号哟,文末有二维码,谢谢!

本文已经是设计模式系列的第三篇文章了,今天来讲讲单例模式、抽象工厂模式和适配器模式。

1、单例模式

单例模式让一个类最多只有一个实例。具体的做法是:

  • 让类的构造方法私有化

  • 提供一个用于获取该类实例的静态公共方法给别的类访问

  • 如果是在多线程的环境下,还要考虑线程安全性,防止产生多个实例

单例模式有多种实现方式:

扫描二维码关注公众号,回复: 13119987 查看本文章
  • 饿汉式

  • 懒汉式

  • 双重检查锁

  • 静态内部类

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中跑一遍,这样印象才会更深呀!

我的二维码

觉得写得不错的小伙伴,扫码关注一下我的公众号吧,谢谢呀!

猜你喜欢

转载自blog.csdn.net/xl_1803/article/details/112007029
今日推荐