设计模式之桥接模式

在 GoF 的《设计模式》一书中,桥接模式被定义为:“将抽象和实现解耦,让它们可以独立变化。”定义中的“抽象”,指的并非“抽象类”或“接口”,而是被抽象出来的一套“类库”,它只包含骨架代码,真正的业务逻辑需要委派给定义中的“实现”来完成。而定义中的“实现”,也并非“接口的实现类”,而是一套独立的“类库”。

应用场景

系统可能有多个维度,每个维度都有可能变化。

类图

类图

将 Color 类组合在 Shape 中来将形状和颜色解耦,各自维护各自的变化,这里体现了组合优于继承的设计原则。

桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。

代码实现

Color

1
2
3
4
5
public abstract class Color {

public abstract void paint(String shape);

}

Red

1
2
3
4
5
6
7
8
public class Red extends Color {

@Override
public void paint(String shape) {
System.out.println("红色的" + shape);
}

}

Green

1
2
3
4
5
6
7
8
public class Green extends Color {

@Override
public void paint(String shape) {
System.out.println("绿色的" + shape);
}

}

Shape

1
2
3
4
5
6
7
8
9
10
11
public abstract class Shape {

protected Color color;

public void setColor(Color color) {
this.color = color;
}

public abstract void draw();

}

Circle

1
2
3
4
5
6
7
8
public class Circle extends Shape {

@Override
public void draw() {
color.paint("圆形");
}

}

Square

1
2
3
4
5
6
7
8
public class Square extends Shape {

@Override
public void draw() {
color.paint("正方形");
}

}

Main

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Main {

public static void main(String[] args) {
Shape shape;
Color color;

color = new Red();
shape = new Circle();
shape.setColor(color);
shape.draw();

color = new Green();
shape = new Square();
shape.setColor(color);
shape.draw();
}

}

至此,我们已经完成了桥接模式的学习。桥接模式在实际项目中并不常用,这里我们了解认识即可。

设计模式之代理模式

代理模式是一种结构型设计模式。结构型模式主要总结了一些类或对象组合在一起的经典结构,这些经典的结构可以解决特定应用场景的问题。结构型模式包括:代理模式、桥接模式、装饰器模式、适配器模式、门面模式、组合模式、享元模式。

代理模式的应用场景

  • 业务系统的非功能性需求开发。比如:监控、统计、鉴权、限流、事务、幂等、日志。我们将这些附加功能与业务功能解耦,放到代理类中统一处理,让程序员只需要关注业务方面的开发。
  • RPC、缓存中应用。RPC 框架也可以看作一种代理模式;假设我们要开发一个接口请求的缓存功能,对于某些接口请求,如果入参相同,在设定的过期时间内,直接返回缓存结果,而不用重新进行逻辑处理。

代理模式分为静态代理和动态代理。静态代理的代理对象,在程序编译时已经写好 Java 文件了,直接 new 一个代理对象即可。动态代理产生代理对象的时机是运行时动态生成,它没有 Java 源文件,直接生成字节码文件实例化代理对象。

静态代理的实现有两种:通过接口和通过继承。

静态代理 - 通过接口方式

类图

通过接口方式的类图

代码实现

UserService

1
2
3
4
5
public interface UserService {

public Boolean login(String username, String password);

}

UserServiceImpl

1
2
3
4
5
6
7
8
public class UserServiceImpl implements UserService {

@Override
public Boolean login(String username, String password) {
return "admin".equals(username) && "admin".equals(password);
}

}

UserServiceProxy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class UserServiceProxy implements UserService {

private final UserService userService;

public UserServiceProxy(UserService userService) {
this.userService = userService;
}

@Override
public Boolean login(String username, String password) {
long t1 = System.nanoTime();
System.out.println("start login");
Boolean result = userService.login(username, password);
System.out.println("end login");
long t2 = System.nanoTime();
System.out.println("time: " + (t2 - t1) + "ns");
return result;
}

}

Main

1
2
3
4
5
6
7
8
9
public class Main {

public static void main(String[] args) {
UserService userService = new UserServiceProxy(new UserServiceImpl());
Boolean result = userService.login("admin", "admin");
System.out.println("result: " + result);
}

}

静态代理 - 通过继承方式

类图

通过继承方式的类图

代码实现

UserService

1
2
3
4
5
public interface UserService {

public Boolean login(String username, String password);

}

UserServiceImpl

1
2
3
4
5
6
7
8
public class UserServiceImpl implements UserService {

@Override
public Boolean login(String username, String password) {
return "admin".equals(username) && "admin".equals(password);
}

}

UserServiceProxy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class UserServiceProxy extends UserServiceImpl {

@Override
public Boolean login(String username, String password) {
long t1 = System.nanoTime();
System.out.println("start login");
Boolean result = super.login(username, password);
System.out.println("end login");
long t2 = System.nanoTime();
System.out.println("time: " + (t2 - t1) + "ns");
return result;
}

}

Main

1
2
3
4
5
6
7
8
9
public class Main {

public static void main(String[] args) {
UserService userService = new UserServiceProxy();
Boolean result = userService.login("admin", "admin");
System.out.println("result: " + result);
}

}

动态代理

上面的代码实现有两个问题:

  1. 我们需要在代理类中,将原始类中的所有的方法,都重新实现一遍,并且为每个方法都附加相似的代码逻辑。
  2. 如果要添加的附加功能的类有不止一个,我们需要针对每个类都创建一个代理类。

这时我们就需要用到动态代理(Dynamic Proxy),就是我们不事先为每个原始类编写代理类,而是在运行的时候,动态地创建原始类对应的代理类,然后在系统中用代理类替换掉原始类。

如何实现动态代理呢?

Java 语言本身就已经提供了动态代理的语法(实际上,动态代理底层依赖的就是 Java 的反射语法)。

类图

动态代理类图

UserService

1
2
3
4
5
public interface UserService {

public Boolean login(String username, String password);

}

UserServiceImpl

1
2
3
4
5
6
7
8
public class UserServiceImpl implements UserService {

@Override
public Boolean login(String username, String password) {
return "admin".equals(username) && "admin".equals(password);
}

}

DynamicProxyHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class DynamicProxyHandler implements InvocationHandler {

private Object target;

public DynamicProxyHandler(Object target) {
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long t1 = System.nanoTime();
System.out.println("start " + target.getClass().getName() + ":" + method.getName());
Object result = method.invoke(target, args);
System.out.println("end " + target.getClass().getName() + ":" + method.getName());
long t2 = System.nanoTime();
System.out.println("time: " + (t2 - t1) + "ns");
return result;
}

}

DynamicProxy

1
2
3
4
5
6
7
8
9
10
public class DynamicProxy {

public Object createProxy(Object target) {
ClassLoader classLoader = target.getClass().getClassLoader();
Class<?>[] interfaces = target.getClass().getInterfaces();
DynamicProxyHandler handler = new DynamicProxyHandler(target);
return Proxy.newProxyInstance(classLoader, interfaces, handler);
}

}

Main

1
2
3
4
5
6
7
8
9
10
public class Main {

public static void main(String[] args) {
DynamicProxy proxy = new DynamicProxy();
UserService userService = (UserService) proxy.createProxy(new UserServiceImpl());
Boolean result = userService.login("admin", "admin");
System.out.println("result: " + result);
}

}

至此,我们已经实现了基于 JDK 的动态代理,JDK 动态代理要求要代理的类必须实现接口,如果类没有实现接口,那么你可以尝试 CGLIB,它不是 JDK 自带的,而是第三方类库,感兴趣可以详细了解。

设计模式之原型模式

使用场景

如果 对象的创建成本比较大,而 同一个类的不同对象之间差别不大(大部分字段都相同),在这种情况下,我们可以利用对已有对象(原型)进行复制(或者叫拷贝)的方式来创建新对象,以达到节省创建时间的目的。

何为“对象的创建成本比较大”?

如果对象中的数据需要经过复杂的计算才能得到(比如排序、计算哈希值),或者需要从 RPC、网络、数据库、文件系统等非常慢速的 IO 中读取,这种情况下,我们就可以利用原型模式,从其他已有对象中直接拷贝得到,而不用每次在创建新对象的时候,都重复执行这些耗时的操作。

原型模式的实现方式:深拷贝和浅拷贝

要使用原型模式,我们就需要对对象进行拷贝,这里我们要先了解下深拷贝和浅拷贝。

浅拷贝和深拷贝的区别在于,浅拷贝只会复制数据的内存地址,而深拷贝会复制数据本身。因此,浅拷贝与原始对象共享数据对象,原始对象如果修改了数据值,拷贝的对象也会变为新的值;而深拷贝得到的是一份完完全全独立的对象,不会受原对象影响。

在 Java 语言中,Object 类的 clone() 方法执行的就是我们刚刚说的浅拷贝。它只会拷贝对象中的基本数据类型的数据(比如,int、long),以及引用对象的内存地址,不会递归地拷贝引用对象本身。

那如何实现深拷贝呢?

  • 递归拷贝对象、对象的引用对象以及引用对象的引用对象……直到要拷贝的对象只包含基本数据类型数据,没有引用对象为止。
  • 先将对象序列化,然后再反序列化成新的对象。

设计模式之建造者模式

使用场景

  • 对象的构建有很多必填参数,如果使用构造函数会导致参数列表过长难以使用
  • 构造参数之间有依赖关系,比如设置了 minAge 就必须设置 maxAge,且 minAge 小于等于 maxAge
  • 类的属性一旦被创建就不可变(不暴力 set()方法)

类图

Person 类包含了一个内部类 Builder,负责对外暴露设置属性的方法,这些方法可以包含校验和初始化规则,属性之前的依赖规则可以放到最终调用的 build()方法中校验

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class Person {

private Long id;
private String name;
private Integer minAge;
private Integer maxAge;

private Person() {
}

public static class Builder {

private Person person = new Person();

public Builder id(Long id) {
person.id = id;
return this;
}

public Builder name(String name) {
person.name = name;
return this;
}

public Builder minAge(Integer minAge) {
person.minAge = minAge;
return this;
}

public Builder maxAge(Integer maxAge) {
person.maxAge = maxAge;
return this;
}

public Person build() {
if (person.minAge != null && person.maxAge != null) {
if (person.minAge < 0) {
throw new IllegalArgumentException("minAge必须大于等于0");
}
if (person.maxAge <= person.minAge) {
throw new IllegalArgumentException("maxAge必须大于等于minAge");
}
} else if ((person.minAge == null && person.maxAge != null) ||
(person.minAge != null && person.maxAge == null)) {
throw new IllegalArgumentException("minAge和maxAge必须同时设置");
}
return person;
}

}

}

与工厂模式有何区别?

  • 工厂模式是用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。
  • 建造者模式是用来创建一种类型的复杂对象,通过设置不同的可选参数,“定制化”地创建不同的对象。

设计模式之工厂模式

工厂模式可以细分为:简单工厂、工厂方法和抽象工厂三种模式

使用场景

总体而言工厂模式的使用场景分为两种:

  1. 单个对象的创建过程比较复杂,如需要做复杂初始化操作的对象
  2. 需要根据不同的类型创建不同的对象

针对细分的三种模式,使用场景又可以区分:

  1. 对象的创建逻辑简单,通常只需要 new 一下就可以,此时可以考虑简单工厂模式
  2. 对象的创建逻辑很复杂,需要做各种初始化操作,此时可以考虑使用工厂方法模式,将对象创建的复杂逻辑拆分到各个工厂类中,让每个工厂类都不至于过于复杂
  3. 系统中有多于一个产品族,而每次只使用其中某一产品族,此时使用抽象工厂模式

简单工厂模式

类图

ProcuctA 和 ProductB 继承 Product 抽象类,ProductFactory 根据传入的 type 来返回不同的 Product 实例

代码实现

Product

1
2
3
public abstract class Product {
public abstract void use();
}

ProductA

1
2
3
4
5
6
public class ProductA extends Product {
@Override
public void use() {
System.out.println("You are using ProductA...");
}
}

ProductB

1
2
3
4
5
6
public class ProductB extends Product {
@Override
public void use() {
System.out.println("You are using ProductB...");
}
}

ProductFactory

1
2
3
4
5
6
7
8
9
10
11
public class ProductFactory {
public Product createProduct(String type) {
Product product = null;
if ("A".equals(type)) {
product = new ProductA();
} else if ("B".equals(type)) {
product = new ProductB();
}
return product;
}
}

Main

1
2
3
4
5
6
7
8
9
10
11
12
public class Main {
public static void main(String[] args) {
ProductFactory factory = new ProductFactory();
Product product;

product = factory.createProduct("A");
product.use();

product = factory.createProduct("B");
product.use();
}
}

点评

当频繁的新增不同产品时,需要频繁的修改ProductFactory中的if/else逻辑

应用

JDK

1
2
3
java.text.DateFormat#getDateInstance()
java.text.DateFormat#getDateInstance(int)
java.text.DateFormat#getDateInstance(int, java.util.Locale)

加密类,获取不同加密算法的密钥生成器:

1
KeyGenerator keyGen=KeyGenerator.getInstance("DESede");

工厂方法模式

类图

相比简单工厂,这种方式将ProductFactory定义为抽象类,然后创建不同的具体的ProductAFactoryProductBFactory,在使用时决定创建那种工厂和对象,避免了 if/else 判断

代码实现

Product

1
2
3
public abstract class Product {
public abstract void use();
}

ProductA

1
2
3
4
5
6
public class ProductA extends Product {
@Override
public void use() {
System.out.println("You are using ProductA...");
}
}

ProductB

1
2
3
4
5
6
public class ProductB extends Product {
@Override
public void use() {
System.out.println("You are using ProductB...");
}
}

ProductFactory

1
2
3
public abstract class ProductFactory {
public abstract Product createProduct();
}

ProductAFactory

1
2
3
4
5
6
public class ProductAFactory extends ProductFactory {
@Override
public Product createProduct() {
return new ProductA();
}
}

ProductBFactory

1
2
3
4
5
6
public class ProductBFactory extends ProductFactory {
@Override
public Product createProduct() {
return new ProductB();
}
}

Main

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Main {

public static void main(String[] args) {
ProductFactory factory;
Product product;

factory = new ProductAFactory();
product = factory.createProduct();
product.use();

factory = new ProductBFactory();
product = factory.createProduct();
product.use();
}

}

点评

这种模式更加符合开闭原则,但是与最原始的new ProductA()new ProductA()相比非常相似,引入工厂模式,反倒让设计变得更加复杂了。

应用

JDBC

1
2
3
Connection conn=DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/db1","db_user","123456");
Statement statement=conn.createStatement();
ResultSet rs=statement.executeQuery("select * from user");

抽象工厂模式

为了理解抽象工厂模式,我们要先了解两个概念:

  • 产品等级结构 :产品等级结构即产品的继承结构,如一个抽象类是电视机,其子类有海尔电视机、海信电视机、TCL 电视机,则抽象电视机与具体品牌的电视机之间构成了一个产品等级结构,抽象电视机是父类,而具体品牌的电视机是其子类。
  • 产品族 :在抽象工厂模式中,产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品,如海尔电器工厂生产的海尔电视机、海尔电冰箱,海尔电视机位于电视机产品等级结构中,海尔电冰箱位于电冰箱产品等级结构中。

在工厂方法模式中具体工厂负责生产具体的产品,每一个具体工厂对应一种具体产品。一般情况下,一个具体工厂中只有一个工厂方法或者一组重载的工厂方法。但是有时候我们需要一个工厂可以提供多个产品对象,而不是单一的产品对象。

类图

Factory1负责创建ProductA1ProductB1
Factory2负责创建ProductA2ProductB2
客户端不需要知道具体的产品,只需要知道创建的工厂即可使用该产品

代码实现

AbstractProductA

1
2
3
4
5
public abstract class AbstractProductA {

public abstract void use();

}

ProductA1

1
2
3
4
5
6
7
8
public class ProductA1 extends AbstractProductA {

@Override
public void use() {
System.out.println("You are using ProductA1...");
}

}

ProductA2

1
2
3
4
5
6
7
8
public class ProductA2 extends AbstractProductA {

@Override
public void use() {
System.out.println("You are using ProductA2...");
}

}

AbstractProductB

1
2
3
4
5
public abstract class AbstractProductB {

public abstract void eat();

}

ProductB1

1
2
3
4
5
6
7
8
public class ProductB1 extends AbstractProductB {

@Override
public void eat() {
System.out.println("You are eating ProductB1...");
}

}

ProductB2

1
2
3
4
5
6
7
8
public class ProductB2 extends AbstractProductB {

@Override
public void eat() {
System.out.println("You are eating ProductB2...");
}

}

AbstractFactory

1
2
3
4
5
6
7
public abstract class AbstractFactory {

public abstract AbstractProductA createProductA();

public abstract AbstractProductB createProductB();

}

Factory1

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Factory1 extends AbstractFactory {

@Override
public AbstractProductA createProductA() {
return new ProductA1();
}

@Override
public AbstractProductB createProductB() {
return new ProductB1();
}

}

Factory2

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Factory2 extends AbstractFactory {

@Override
public AbstractProductA createProductA() {
return new ProductA2();
}

@Override
public AbstractProductB createProductB() {
return new ProductB2();
}

}

Main

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Main {

public static void main(String[] args) {
AbstractFactory factory;

factory = new Factory1();
use(factory);

factory = new Factory2();
use(factory);
}

public static void use(AbstractFactory factory) {
AbstractProductA productA = factory.createProductA();
productA.use();
AbstractProductB productB = factory.createProductB();
productB.eat();
}

}

点评

抽象工厂模式带来了众多类,结构相对复杂,且创建新的产品等级结构麻烦。
抽象工厂模式实际工作中应用场景很少,简单关注了解下即可

设计模式之单例模式

单例设计模式理解起来非常简单。一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫单例模式。

使用场景

处理资源访问冲突

下面的示例中如果每个类都创建一个 Logger 实例,就可能造成日志内容被覆盖的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Logger {
private FileWriter writer;

public Logger() {
File file = new File("log.txt");
writer = new FileWriter(file, true); //true表示追加写入
}

public void log(String message) {
writer.write(mesasge);
}
}


public class UserController {
private Logger logger = new Logger();

public void login(String username, String password) {
// ...省略业务逻辑代码...
logger.log(username + " logined!");
}
}

public class OrderController {
private Logger logger = new Logger();

public void create(OrderVo order) {
// ...省略业务逻辑代码...
logger.log("Created an order: " + order.toString());
}
}

表示全局唯一类

如果有些数据在系统中只应保存一份,那就比较适合设计为单例类。比如,配置信息类,全局 ID 生成器等。

如何实现一个单例?

要实现一个单例,我们要考虑以下几点:

  • 构造函数需要是 private 访问权限的,这样才能避免外部通过 new 创建实例;
  • 考虑对象创建时的线程安全问题;
  • 考虑是否支持延迟加载;
  • 考虑 getInstance() 性能是否高(是否加锁)。

饿汉式

1
2
3
4
5
6
7
8
9
public class Singleton {
private static final Singleton instance = new Singleton();

private Singleton() {}

public static Singleton getInstance() {
return instance;
}
}

懒汉式

懒汉式相对于饿汉式的优势是支持延迟加载。但缺点也很明显,因为使用了synchronized关键字导致这个方法的并发度很低。如果这个单例类偶尔会被用到,那这种实现方式还可以接受。但是,如果频繁地用到,就会导致性能瓶颈,这种实现方式就不可取了。

1
2
3
4
5
6
7
8
9
10
11
12
public class Singleton {
private static Singleton instance;

private Singleton() {}

public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

双重检测

这是一种既支持延迟加载、又支持高并发的单例实现方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Singleton {
private static Singleton instance;

private Singleton() {}

public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) { // 此处为类级别的锁
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

在 java1.5 以下instance = new Singleton();有指令重排问题,需要给instance成员变量加上volatile关键字,java1.5 之后不会再这个有问题。

静态内部类

这种方式利用了 Java 的静态内部类,有点类似饿汉式,但又能做到了延迟加载。

当外部类 Singleton 被加载的时候,并不会创建 SingletonHolder 实例对象。只有当调用 getInstance() 方法时,SingletonHolder 才会被加载,这个时候才会创建 instance。insance 的唯一性、创建过程的线程安全性,都由 JVM 来保证。所以,这种实现方法既保证了线程安全,又能做到延迟加载。

1
2
3
4
5
6
7
8
9
10
11
public class Singleton {
private Singleton() {}

private static class SingletonHolder{
private static final Singleton instance = new Singleton();
}

public static Singleton getInstance() {
return SingletonHolder.instance;
}
}

枚举

这是一种最简单的实现方式,基于枚举类型的单例实现。这种实现方式通过 Java 枚举类型本身的特性,保证了实例创建的线程安全性和实例的唯一性。

1
2
3
4
5
6
7
8
public enum IdGenerator {
INSTANCE;
private AtomicLong id = new AtomicLong(0);

public long getId() {
return id.incrementAndGet();
}
}

如何实现线程唯一的单例?

上面的单例类对象是进程唯一的,一个进程只能有一个单例对象。那如何实现一个线程唯一的单例呢?

假设 IdGenerator 是一个线程唯一的单例类。在线程 A 内,我们可以创建一个单例对象 a。因为线程内唯一,在线程 A 内就不能再创建新的 IdGenerator 对象了,而线程间可以不唯一,所以,在另外一个线程 B 内,我们还可以重新创建一个新的单例对象 b。

我们通过一个 ConcurrentHashMap 来存储对象,其中 key 是线程 ID,value 是对象。这样我们就可以做到,不同的线程对应不同的对象,同一个线程只能对应一个对象。实际上,Java 语言本身提供了 ThreadLocal 工具类,可以更加轻松地实现线程唯一单例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class IdGenerator {
private AtomicLong id = new AtomicLong(0);

private static final ConcurrentHashMap<Long, IdGenerator> instances
= new ConcurrentHashMap<>();

private IdGenerator() {}

public static IdGenerator getInstance() {
Long currentThreadId = Thread.currentThread().getId();
instances.putIfAbsent(currentThreadId, new IdGenerator());
return instances.get(currentThreadId);
}

public long getId() {
return id.incrementAndGet();
}
}

设计模式之设计原则

SOLID 原则是由五个设计原则组成:单一职责原则(SRP),开闭原则(OCP),里式替换原则(LSP),接口隔离原则(ISP),依赖反转原则(DIP)

单一职责原则(SRP)

概念

单一职责原则的英文是 Single Responsibility Principle,缩写为 SRP。

一个类只负责完成一个职责或者功能。不要设计大而全的类,要设计粒度小、功能单一的类。单一职责原则是为了实现代码高内聚、低耦合,提高代码的复用性、可读性、可维护性。

如何判断类的职责是否足够单一?

不同的应用场景、不同阶段的需求背景、不同的业务层面,对同一个类的职责是否单一,可能会有不同的判定结果。

一些侧面的判断指标更具有指导意义和可执行性,比如,出现下面这些情况就有可能说明这类的设计不满足单一职责原则:

  • 类中的代码行数、函数或者属性过多;

  • 类依赖的其他类过多,或者依赖类的其他类过多;

  • 私有方法过多;

  • 比较难给类起一个合适的名字;

  • 类中大量的方法都是集中操作类中的某几个属性。

类的职责是否设计得越单一越好?

单一职责原则是为了实现代码高内聚、低耦合,如果拆分得过细,实际上会适得其反,反倒会降低内聚性,也会影响代码的可维护性。

开闭原则(OCP)

概念

开闭原则的英文全称是 Open Closed Principle,简写为 OCP。

软件实体(模块、类、方法等)应该“对扩展开放、对修改关闭”

添加一个新的功能,应该是通过在已有代码基础上扩展代码(新增模块、类、方法、属性等),而非修改已有代码(修改模块、类、方法、属性等)的方式来完成。关于定义,我们有两点要注意。第一点是,开闭原则并不是说完全杜绝修改,而是以最小的修改代码的代价来完成新功能的开发。第二点是,同样的代码改动,在粗代码粒度下,可能被认定为“修改”;在细代码粒度下,可能又被认定为“扩展”。

如何做到“对扩展开放、修改关闭”?

我们要时刻具备扩展意识、抽象意识、封装意识,在写代码的时候,多思考这段代码未来可能有哪些需求变更,如何设计代码结构,事先留好扩展点,以便将新的代码灵活地插入到扩展点上。

23 种经典设计模式,大部分都是为了解决代码的扩展性问题而总结出来的,都是以开闭原则为指导原则的。最常用来提高代码扩展性的方法有:多态、依赖注入、基于接口而非实现编程,以及大部分的设计模式(比如,装饰、策略、模板、职责链、状态)。

里式替换原则(LSP)

概念

里式替换原则的英文翻译是:Liskov Substitution Principle,缩写为 LSP。

子类对象能够替换程序中父类对象出现的任何地方,并且保证原来程序的逻辑行为不变及正确性不被破坏。

里式替换原则是用来指导,继承关系中子类该如何设计的一个原则。理解里式替换原则,最核心的就是理解“design by contract,按照协议来设计”这几个字。父类定义了函数的“约定”(或者叫协议),那子类可以改变函数的内部实现逻辑,但不能改变函数原有的“约定”。这里的约定包括:函数声明要实现的功能;对输入、输出、异常的约定;甚至包括注释中所罗列的任何特殊说明。

里式替换原则跟多态的区别

虽然从定义描述和代码实现上来看,多态和里式替换有点类似,但它们关注的角度是不一样的。多态是面向对象编程的一大特性,也是面向对象编程语言的一种语法。它是一种代码实现的思路。而里式替换是一种设计原则,用来指导继承关系中子类该如何设计,子类的设计要保证在替换父类的时候,不改变原有程序的逻辑及不破坏原有程序的正确性。

接口隔离原则(ISP)

概念

接口隔离原则的英文翻译是“ Interface Segregation Principle”,缩写为 ISP。

客户端不应该强迫依赖它不需要的接口。其中的“客户端”,可以理解为接口的调用者或者使用者。

接口的设计要尽量单一,不要让接口的实现类和调用者,依赖不需要的接口函数。

接口隔离原则与单一职责原则的区别

单一职责原则针对的是模块、类、接口的设计。接口隔离原则相对于单一职责原则,一方面更侧重于接口的设计,另一方面它的思考角度也是不同的。接口隔离原则提供了一种判断接口的职责是否单一的标准:通过调用者如何使用接口来间接地判定。如果调用者只使用部分接口或接口的部分功能,那接口的设计就不够职责单一。

依赖反转原则(DIP)

概念

依赖反转原则。依赖反转原则的英文翻译是 Dependency Inversion Principle,缩写为 DIP。

高层模块不要依赖低层模块。高层模块和低层模块应该通过抽象来互相依赖。除此之外,抽象不要依赖具体实现细节,具体实现细节依赖抽象。

所谓高层模块和低层模块的划分,简单来说就是,在调用链上,调用者属于高层,被调用者属于低层。

控制反转(IOC)

这里的“控制”指的是对程序执行流程的控制,而“反转”指的是在没有使用框架之前,程序员自己控制整个程序的执行。在使用框架之后,整个程序的执行流程可以通过框架来控制。流程的控制权从程序员“反转”到了框架。

实现控制反转的方法有很多,控制反转并不是一种具体的实现技巧,而是一个比较笼统的设计思想,一般用来指导框架层面的设计。

依赖注入(DI)

什么是依赖注入呢?我们用一句话来概括就是:不通过 new() 的方式在类内部创建依赖类对象,而是将依赖的类对象在外部创建好之后,通过构造函数、函数参数等方式传递(或注入)给类使用。

KISS 原则

概念

KISS 原则。英文是 Keep It Simple and Stupid,缩写为 KISS。

尽量保持简单

KISS 原则中的“简单”并不是以代码行数来考量的。代码行数越少并不代表代码越简单,我们还要考虑逻辑复杂度、实现难度、代码的可读性等。而且,本身就复杂的问题,用复杂的方法解决,并不违背 KISS 原则。除此之外,同样的代码,在某个业务场景下满足 KISS 原则,换一个应用场景可能就不满足了。

对于如何写出满足 KISS 原则的代码

  • 不要使用同事可能不懂的技术来实现代码;

  • 不要重复造轮子,要善于使用已经有的工具类库;

  • 不要过度优化。

DRY 原则

概念

DRY 原则为 Don’t Repeat Yourself

不要重复造轮子

实现逻辑重复,但功能语义不重复的代码,并不违反 DRY 原则。实现逻辑不重复,但功能语义重复的代码,也算是违反 DRY 原则。除此之外,代码执行重复也算是违反 DRY 原则。

提高代码可复用性的一些方法

  • 减少代码耦合

  • 满足单一职责原则

  • 模块化

  • 业务与非业务逻辑分离

  • 通用代码下沉

  • 继承、多态、抽象、封装

  • 应用模板等设计模式

我们在第一次写代码的时候,如果当下没有复用的需求,而未来的复用需求也不是特别明确,并且开发可复用代码的成本比较高,那我们就不需要考虑代码的复用性。在之后开发新的功能的时候,发现可以复用之前写的这段代码,那我们就重构这段代码,让其变得更加可复用。

相比于代码的可复用性,DRY 原则适用性更强一些。我们可以不写可复用的代码,但一定不能写重复的代码。

迪米特法则(LOD)

概念

迪米特法则的英文翻译是:Law of Demeter,缩写是 LOD。它还有另外一个更加达意的英文翻译为:The Least Knowledge Principle。

最小知识原则

每个模块只应该了解那些与它关系密切的模块的有限知识。

不该有直接依赖关系的类之间,不要有依赖。有依赖关系的类之间,尽量只依赖必要的接口。迪米特法则是希望减少类之间的耦合,让类越独立越好。每个类都应该少了解系统的其他部分。一旦发生变化,需要了解这一变化的类就会比较少。

如何理解“高内聚、松耦合”?

所谓高内聚,就是指相近的功能应该放到同一个类中,不相近的功能不要放到同一类中。相近的功能往往会被同时修改,放到同一个类中,修改会比较集中。

所谓松耦合指的是,在代码中,类与类之间的依赖关系简单清晰。即使两个类有依赖关系,一个类的代码改动也不会或者很少导致依赖类的代码改动。

最重要的JVM参数指南

概述

在这个快速教程中,我们将探索可用于配置 Java 虚拟机的最知名的选项。

堆内存 -Xms 和 -Xmx

最常见的与性能相关的实践之一是根据应用程序需求初始化堆内存。

这就是为什么我们应该指定最小和最大的堆大小:

1
2
-Xms<heap size>[unit]
-Xmx<heap size>[unit]
阅读全文

Java Volatile关键字

原文地址 作者:Jakob Jenkov 译者:小龙虾

转载自并发编程网 – ifeve.com

Java 的 volatile 关键字用于标记一个变量“应当存储在主存”。更确切地说,每次读取 volatile 变量,都应该从主存读取,而不是从 CPU 缓存读取。每次写入一个 volatile 变量,应该写到主存中,而不是仅仅写到 CPU 缓存。

实际上,从 Java 5 开始,volatile 关键字除了保证 volatile 变量从主存读写外,还提供了更多的保障。我将在后面的章节中进行说明。

阅读全文

The java.util.concurrent Synchronizer Framework 中文翻译版

原文:https://raw.githubusercontent.com/sunlggggg/public/master/files/aqs.pdf

作者:Doug Lea

摘要

在 J2SE 1.5 的 java.util.concurrent 包(下称 j.u.c 包)中,大部分的同步器(例如锁,屏障等等)都是基于 AbstractQueuedSynchronizer 类(下称 AQS 类),这个简单的框架而构建的。这个框架为同步状态的原子性管理、线程的阻塞和解除阻塞以及排队提供了一种通用的机制。这篇论文主要描述了这个框架基本原理、设计、实现、用法以及性能。

阅读全文