引言

 

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

 

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

 

我的二维码

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

设计模式之单例模式、适配器模式、抽象工厂模式_编程开发