六大设计原则
一、单一职责原则
1、概念描述
单一职责原则要求一个接口或类只有一个原因引起变化,也就是说一个接口或一个类只有一个原则,它就只负责一件事。
2、案例演示
这里基于方法和类的细化都可以,可以根据实际业务选择。
class Animal {
public void dogVoice (){
System.out.println("狗叫声:旺旺");
}
public void cowVoice (){
System.out.println("牛叫声:哞哞");
}
}
class DogVoice {
public String getDogVoice (){
return "旺旺" ;
}
}
class CowVoice {
public String getCowVoice (){
return "哞哞" ;
}
}
3、注意事项
减少代码一处变更引起的程序大规模改动情况,降低类的复杂度,提高类的可读性,可维护性。通常情况下,需要遵守单一职责原则,可以适当违反单一职责原则。
二、接口隔离原则
1、概念描述
用于处理胖接口(fat interface)所带来的问题。如果类的接口定义暴露了过多的行为,则说明这个类的接口定义内聚程度不够好。换句话说,类的接口可以被分解为多组功能函数的组合,每一组都服务于不同的客户类,而不同的客户类可以选择使用不同的功能分组。
2、案例演示
interface ReadBlog {
String getBlog () ;
}
interface AdminBlog {
Boolean insertBlog () ;
Boolean updateBlog () ;
Boolean deleteBlog () ;
}
/**
* 读者只开放博客阅读接口
*/
class Reader implements ReadBlog {
@Override
public String getBlog() {
return null;
}
}
/**
* 管理员有博客全部的管理权限
*/
class AdminUser implements AdminBlog,ReadBlog {
@Override
public String getBlog() {
return null;
}
@Override
public Boolean insertBlog() {
return null;
}
@Override
public Boolean updateBlog() {
return null;
}
@Override
public Boolean deleteBlog() {
return null;
}
}
3、注意事项
接口的设计粒度越小,则应用系统程序越灵活,程序变得灵活也就意味同时结构复杂性提高,开发开发和理解的难度也会变大,可维护性降低。
三、依赖倒转原则
1、概念描述
1、上层模块不应该依赖底层模块,它们都应该依赖于抽象。
2、抽象不应该依赖于细节,细节应该依赖于抽象。
高层模块不应该依赖低层模块,两者应依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象;中心思想是面向接口编程。
2、案例演示
public class C01_FarmFactory {
public static void main(String[] args) {
Animal animal = new Dog() ;
FarmFactory farm = new Farming() ;
farm.breed(animal) ;
animal = new Pig() ;
farm.breed(animal) ;
}
}
/**
* 接口声明依赖对象
*/
interface FarmFactory {
void breed (Animal animal) ;
}
class Farming implements FarmFactory {
@Override
public void breed(Animal animal) {
System.out.println("农场饲养:"+animal.getAnimalName());
}
}
interface Animal {
String getAnimalName () ;
}
class Dog implements Animal {
@Override
public String getAnimalName() {
return "牧羊犬";
}
}
class Pig implements Animal {
@Override
public String getAnimalName() {
return "土猪一号";
}
}
3、注意事项
相对于系统开发的多变性,抽象的相对稳定。以抽象为基础搭建的架构比以细节为基础的架构要稳定灵活。下层模块尽量都要有抽象类或接口,程序稳定性更好。变量的声明类型尽量是抽象类或接口,这样变量引用和实际对象之间存在一个过渡空间,利于程序扩展和优化。
四、里氏替换原则
1、概念描述
假设如下场景:
- 存在,一个类型T1,和实例的对象O1
- 存在,一个类型T2,和实例的对象O2
如果将所有类型为T1的对象O1都替换成类型T2的对象O2,程序的行为不发生改变。那么类型T2是类型T1的子类型。换句话说,所有引用基类的地方必须能透明地使用其子类的对象。
2、案例演示
public class C01_Calculate {
public static void main(String[] args) {
BizCalculate bizCalculate = new BizCalculate() ;
System.out.println(bizCalculate.add(2,3));
}
}
class Calculate { }
class BaseCalculate extends Calculate {
public int add (int a,int b){
return a+b;
}
}
/**
* 这里使用组合的方式完成计算
*/
class BizCalculate extends Calculate {
private BaseCalculate baseCalculate = new BaseCalculate() ;
public int add (int a,int b){
return this.baseCalculate.add(a,b);
}
}
3、注意事项
使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法;子类可以扩展父类的功能,但不能改变原有父类的功能;在适当的情况下,可以通过聚合,组合,依赖等方式解决问题。
五、开闭原则
1、概念描述
开闭原则是编程中最基础、最重要的设计原则,在代码结构的设计设计时,应该考虑对扩展开放,对修改关闭,抽象思维搭建结构,具体实现扩展细节。
2、案例演示
public class C01_BookPrice {
public static void main(String[] args) {
ParityBook parityBook = new DiscountBook("Java",100.00) ;
System.out.println(parityBook.getPrice());
}
}
interface Book {
String getName () ;
Double getPrice () ;
}
/**
* 平价书籍
*/
class ParityBook implements Book {
private String name ;
private Double price ;
public ParityBook(String name, Double price) {
this.name = name;
this.price = price;
}
@Override
public String getName() {
return this.name ;
}
@Override
public Double getPrice() {
return this.price ;
}
}
/**
* 打折数据扩展价格计算策略
*/
class DiscountBook extends ParityBook {
public DiscountBook(String name, Double price) {
super(name, price);
}
@Override
public Double getPrice() {
double oldPrice = super.getPrice();
return oldPrice * 0.8 ;
}
}
3、注意事项
基于开闭原则设计的代码结构可以提高复用性和可维护性,通过接口或抽象类可以约束类的变化行为,基于指定策略对变化行为进行封装,并且能够实现对扩展开放,使用设计模式的基本原则就是遵循开闭原则。
六、迪米特原则
1、概念描述
迪米特原则又叫最少知道原则,即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供的public方法,不对外开放任何信息。类与类关系越密切,耦合度越大,耦合的方式很多,依赖,关联,组合,聚合等。
- 直接朋友概念
两个对象之间有耦合关系,就说这两个对象之间是朋友关系。其中出现成员变量,方法参数,方法返回值中的类称为直接朋友,而出现在局部变量中的类不是直接朋友。从原则上说,陌生的类最好不要以局部变量的形式出现在类的内部。
2、案例演示
public class C01_Employee {
public static void main(String[] args) {
HeadCompanyEmpManage empManage = new HeadCompanyEmpManage() ;
BranchCompanyEmpManage branchEmp = new BranchCompanyEmpManage() ;
empManage.printEmp(branchEmp);
}
}
/**
* 总公司员工
*/
class HeadCompanyEmp {
public String name ;
public HeadCompanyEmp(String name) {
this.name = name;
}
@Override
public String toString() {
return "HeadCompanyEmp{name='" + name + '}';
}
}
/**
* 分公司员工
*/
class BranchCompanyEmp {
public String name ;
public BranchCompanyEmp(String name) {
this.name = name;
}
@Override
public String toString() {
return "BranchCompanyEmp{name='" + name + '}';
}
}
/**
* 分公司员工管理
*/
class BranchCompanyEmpManage {
// 添加分公司员工
public List<BranchCompanyEmp> addEmp (){
List<BranchCompanyEmp> list = new ArrayList<>() ;
for (int i = 1 ; i <= 3 ; i++){
list.add(new BranchCompanyEmp("分公司员工"+i)) ;
}
return list ;
}
// 获取分公司员工
public void printBranchCompanyEmp (){
List<BranchCompanyEmp> list = addEmp () ;
for (BranchCompanyEmp emp:list){
System.out.println(emp);
}
}
}
/**
* 总公司员工管理,基于迪米特原则,不出现陌生类
*/
class HeadCompanyEmpManage {
// 添加总公司员工
public List<HeadCompanyEmp> addHeadEmp (){
List<HeadCompanyEmp> list = new ArrayList<>() ;
for (int i = 1 ; i <= 3 ; i++){
list.add(new HeadCompanyEmp("总公司员工"+i)) ;
}
return list ;
}
public void printEmp (BranchCompanyEmpManage empManage){
// 打印分公司员工
empManage.printBranchCompanyEmp();
List<HeadCompanyEmp> headEmpList = addHeadEmp () ;
for (HeadCompanyEmp headCompanyEmp:headEmpList){
System.out.println(headCompanyEmp);
}
}
}
3、注意事项
迪米特原则的初衷是降低类之间的耦合,由于每个类都减少了不必要的依赖,因此可以降低耦合关系。降低耦合关系,并不是要求完全没有依赖关系,过度的使用迪米特原则,容易产生大量的中间类,导致复杂度变大。所以在使用迪米特原则时要根据实际业务权衡。
七、设计原则总结
设计模式和设计原则的核心思想都是:判断业务应用中可能会变化模块,并且把这些模块独立出来,基于指定的策略进行封装,不要和那些变化的不大的模块耦合在一起,封装思想上基于接口和抽象类,而不是针对具体的实现编程。核心目的就是降低交互对象之间的松耦合度。设计模式和原则都不是可以生搬硬套的公式,个人理解:只要形似,神韵就自然不差。
23种设计模式
单例模式
一、简介
1、概念图解
单例设计模式定义:确保这个类只有一个实例,并且自动的实例化向系统提供这个对象。
2、样例代码
package com.model.test;
public class Singleton {
// 使用静态变量记录唯一实例
private static Singleton singleton = null;
private Singleton (){}
public static Singleton getInstance (){
if (singleton == null){
singleton = new Singleton() ;
}
return singleton ;
}
public static void main(String[] args) {
Singleton singleton1 = Singleton.getInstance() ;
Singleton singleton2 = Singleton.getInstance() ;
/**
* com.model.test.Singleton@15db9742
* com.model.test.Singleton@15db9742
*/
System.out.println(singleton1);
System.out.println(singleton2);
}
}
Singleton称为单例类,构造函数使用private修饰,确保系统中只能产生一个实例,并且自动生成的。上面代码也就是所谓的懒汉式加载:只有到使用该对象的时候才来创建,意思饿了才来做饭吃。
二、线程安全问题
在上面的代码中存在一个很明显的线程安全问题,当有多条线程来请求对象实例的时候,因为对象的创建是需要时间的,假设A线程进来判断singleton == null,就会进入对象的创建过程,这时如果同时在过来几条线程,那么他们都会得到一个对象实例,这个就是所谓的线程安全问题。
1、同步控制方式
package com.model.test;
public class Singleton {
// 使用静态变量记录唯一实例
private static Singleton singleton = null;
private Singleton (){}
public static synchronized Singleton getInstance (){
if (singleton == null){
singleton = new Singleton() ;
}
return singleton ;
}
}
这样操作会影响系统性能
2、饿汉式加载
public class Singleton {
// 使用静态变量记录唯一实例
private static Singleton singleton = new Singleton();
private Singleton (){}
public static Singleton getInstance (){
return singleton ;
}
}
这里先把对象创建出来,有需要直接使用;
3、双重检查
public class Singleton {
// 使用静态变量记录唯一实例
// volatile可以确保当singleton被初始化后,多线程才可以正确处理
// 被volatile修饰的变量的值,将不会被本地线程缓存
// 对该变量读写都是直接操作共享内存,确保多个线程能正确的处理该变量。
private static volatile Singleton singleton = null ;
private Singleton (){}
public static Singleton getInstance (){
// 如果实例不存在,则进入同步区
if (singleton == null){
// 只有第一次才会彻底执行这里面的代码
synchronized (Singleton.class) {
if (singleton == null){
singleton = new Singleton() ;
}
}
}
return singleton ;
}
}
4、枚举方式
package com.model.design.base.node01.singleton;
import org.junit.Test;
/**
* 类级内部类里面创建对象实例
*/
public class C06_Singleton {
@Test
public void test01 (){
SingletonDemo INSTANCE1 = SingletonDemo.INSTANCE ;
SingletonDemo INSTANCE2 = SingletonDemo.INSTANCE ;
System.out.println(INSTANCE1 == INSTANCE2);
INSTANCE1.info();
INSTANCE2.info();
}
}
enum SingletonDemo {
INSTANCE ;
public void info (){
System.out.println("枚举方式实现单例");
}
}
三、延迟类初始化
1、基础概念
1)、类级内部类
简单点说,类级内部类指的是,有static修饰的成员式内部类。如果没有static修饰的成员式内部类被称为对象级内部类。
类级内部类相当于其外部类的static成分,它的对象与外部类对象间不存在依赖关系,因此可直接创建。而对象级内部类的实例,是绑定在外部对象实例中的。
类级内部类中,可以定义静态的方法。在静态方法中只能够引用外部类中的静态成员方法或者成员变量。
类级内部类相当于其外部类的成员,只有在第一次被使用的时候才被会装载。
2)、多线程缺省同步锁
在多线程开发中,为了解决并发问题,主要是通过使用synchronized来加互斥锁进行同步控制。但是在某些情况中,JVM已经隐含地执行了同步,这些情况下就不用自己再来进行同步控制了。这些情况包括:
1.由静态初始化器(在静态字段上或static{}块中的初始化器)初始化数据时
2.访问final字段时
3.在创建线程之前创建对象时
4.线程可以看见它将要处理的对象时
2、实现方式
要想很简单地实现线程安全,可以采用静态初始化器的方式,它可以由JVM来保证线程的安全性。比如前面的饿汉式实现方式,在类装载的时候就初始化对象,不管是否需要,存在一定的空间浪费。
一种可行的方式就是采用类级内部类,在这个类级内部类里面去创建对象实例。这样一来,只要不使用到这个类级内部类,那就不会创建对象实例,从而同时实现延迟加载和线程安全。
public class LazySingleton {
/**
* 类级内部类
*/
private static class SingletonHolder {
private static LazySingleton lazySingleton = new LazySingleton() ;
}
public static LazySingleton getInstance (){
return SingletonHolder.lazySingleton ;
}
public static void main(String[] args) {
LazySingleton lazySingleton1 = LazySingleton.getInstance() ;
LazySingleton lazySingleton2 = LazySingleton.getInstance() ;
/**
* com.model.test.LazySingleton@15db9742
* com.model.test.LazySingleton@15db9742
*/
System.out.println(lazySingleton1+";;"+lazySingleton2);
}
}
四、JDK源码应用
Runtime单例实现源码。
1、案例演示
/**
* JDK 单例模式分析
*/
public class C07_Singleton {
public static void main(String[] args) {
Runtime runtime1 = Runtime.getRuntime() ;
Runtime runtime2 = Runtime.getRuntime() ;
/*
* 1229416514
* 1229416514
*/
System.out.println(runtime1.hashCode());
System.out.println(runtime2.hashCode());
}
}
2、源代码分析
public class Runtime {
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
private Runtime() {}
}
基于饿汉模式实现的单例模式。
五、Spring框架应用
1、创建测试类
public class UserBean {
}
2、Spring配置文件
<!-- 单例Bean -->
<bean id="user"
class="com.model.design.spring.node01.singleton.UserBean" />
3、测试读取Bean对象
package com.model.design.spring.node01.singleton;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Spring框架中单例模式
*/
public class S01_Singleton {
@Test
public void test01 (){
ApplicationContext context01 = new ClassPathXmlApplicationContext("/spring/spring-context.xml");
ApplicationContext context02 = new ClassPathXmlApplicationContext("/spring/spring-context.xml");
UserBean user01 = (UserBean)context01.getBean("user") ;
UserBean user02 = (UserBean)context01.getBean("user") ;
UserBean user03 = (UserBean)context02.getBean("user") ;
// com.model.design.spring.node01.singleton.UserBean@364841
System.out.println(user01);
// com.model.design.spring.node01.singleton.UserBean@364841
System.out.println(user02);
// com.model.design.spring.node01.singleton.UserBean@c4711c
System.out.println(user03);
}
}
结论
Spring单例模式与纯粹的单例设计模式的主要区别
尽管使用相同的类加载器来加载两个应用程序上下文,但是UserBean的实例是不一样的。也就是Spring框架中的单例对象是基于应用程序中。
六、单例模式总结
1、注意事项
单例模式注意事项和细节说明
1) 单例模式保证了 系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能。
2) 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new Object() 的方式。
3) 单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象。
2、优缺点
优点:
1、单例模式只会创建一个对象实例,减少内存消耗
2、设置全局访问点,优化共享资源的访问
缺点:
1、没有接口,很难扩展
2、不利于测试
3、与单一职责原则冲突