1、设计模式七大原则
设计模式体现了代码的耦合性, 内聚性以及可维护性,可扩展性,重用性,灵活性。
- 1、代码重用性(即:相同功能的代码,不用多次编写)
- 2、可读性(即:编程规范性,便于其他程序员的阅读和理解)
- 3、可扩展性(即:当需要增加新的功能时,非常的方便,称为可维护)
- 4、可靠性(即:当我们增加新的功能后,对原来的功能没有影响)
- 5、使程序呈现
高内聚,低耦合
的特性
Scott Mayers在其巨著《Effective C++》说过: C++老手和C++新手的区别就是前者手背上有很多伤疤。
- 同样是面向对象,当然也可指 Java
一、单一职责原则(Single responsibility)
单一职责原则注意事项和细节:
- 1、降低类的复杂度,一个类只负责一项职责;
- 2、提高类的可读性,可维护性;
- 3、降低变更引起的风险;
- 4、通常情况下,应当遵守单一职责原则, 只有逻辑足够简单,才可以在方法级违反单一职责原则。
/**
* 只有类中方法数量足够少,可以在方法级别保持单一职责原则
*/
public class Singleresponsibility {
public static void main(String[] args) {
Vehicle vehicle = new Vehicle();
vehicle.run("跑跑卡丁车");
vehicle.fly("直升飞机");
}
}
//交通工具类
// 逻辑简单,方法级别实现单一职责
// 逻辑复杂,分类实现单一职责
class Vehicle{
public void run(String vehicle){
System.out.println(vehicle+"在陆地上跑");
}
public void fly(String vehicle){
System.out.println(vehicle+"在天空上飞");
}
}
二、接口隔离原则(Interface Segregation)
- 1、类A通过接口 Interface1、2 依赖类B,类C通过接口 Interface1、3 依赖类D,如果接口 Interface 对于 类A 和 类C 来说不是最小接口,那么 类B 和 类D 必须去实现他们不需要的方法。
- 2、将接口 Interface 拆分为独立的几个接口,类A 和 类C 分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则。
- 3、接口 Interface 中出现的方法,根据实际情祝拆分为三个接口。
public class Segregation {
public static void main(String[] args) {
A a = new A();
a.depent1(new B());//A依赖B
a.depent2(new B());
a.depent3(new B());
C c = new C();
c.depent1(new D());//C依赖D
c.depent2(new D());
c.depent3(new D());
}
}
interface interface1{
void operation1();
}
interface interface2{
void operation2();
void operation3();
}
interface interface3{
void operation4();
void operation5();
}
class B implements interface1,interface2{
@Override
public void operation1() {
System.out.println("b实现了operation1");
}
@Override
public void operation2() {
System.out.println("b实现了operation2");
}
@Override
public void operation3() {
System.out.println("b实现了operation3");
}
}
class D implements interface1,interface3{
@Override
public void operation1() {
System.out.println("D实现了operation1");
}
@Override
public void operation4() {
System.out.println("D实现了operation4");
}
@Override
public void operation5() {
System.out.println("D实现了operation5");
}
}
class A{
void depent1(interface1 i){
i.operation1();
}
void depent2(interface2 i){
i.operation2();
}
void depent3(interface2 i){
i.operation3();
}
}
class C{
void depent1(interface1 i){
i.operation1();
}
void depent2(interface3 i){
i.operation4();
}
void depent3(interface3 i){
i.operation5();
}
三、依赖倒转原则(Dependence Inversion)
-
1、高层模块不应该依赖低层模块,二者都应该依赖其抽象(缓冲层);
-
2、抽象不应该依赖细节,细节应该依赖抽象;
-
3、依赖倒转(倒置)的中心思想是面向接口编程;
-
4、依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。在java中, 抽象指的是接口或抽象类,细节就是具体的实现类;
-
5、使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。
-
依赖关系三种传递方式:
- 接口传递(依赖)
- 构造方法传递(依赖)
- setter方式传递(聚合)
//依赖倒转原则
public class DependenceInversion {
public static void main(String[] args) {
persion persion = new persion();
persion.receve(new Email());
persion.receve(new Wechat());
}
}
interface IRecever{
String getInfo();
}
class Email implements IRecever{
@Override
public String getInfo() {
return "email:helloworld";
}
}
class Wechat implements IRecever{
@Override
public String getInfo() {
return "Wechat:helloworld";
}
}
//模拟人收到消息
class persion{
//接受类型为接口
public void receve(IRecever recever){
String info = recever.getInfo();
System.out.println(info);
}
}
四、里氏替换原则(Liskov Substitution)
-
1、里氏替换原则(Liskov Substitution Principle)在1988年,由麻省理工学院一位姓里的女士提出;
-
2、如果对每个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型。换句话说,所有引用基类的地方必须能透明地使用其子类的对象;
-
3、在使用继承时,遵循里氏替换原则,
在子类中尽量不要重写父类的方法
; -
4、
继承实际上让两个类耦合性增强了,给程序带来侵入性。在适当的情况下,可以通过聚合,组合,依赖来解决问题;
-
5、继承包含这样一层含义:父类中凡是已经实现好的方法,实际上是在设定规范和契约,虽然它不强制要求所有的子类必须遵循这些契约,但是如果子类对这些已经实现的方法任意修改,就会对整个继承体系造成破坏。
public class LiskovSubstitution { public static void main(String[] args) { A a = new A(); System.out.println("2-1=" + a.func1(2, 1)); B b = new B(); System.out.println("2+1=" + b.func1(2, 1)); System.out.println("2+1+9=" + b.func2(2, 1)); System.out.println("B类使用A类方法:2-1=" + b.func3(2, 1)); } } class Base { //把基础方法和成员抽取成基类 public int func1(int num1, int num2) { return num1 - num2; } } class A extends Base { // public int func1(int num1, int num2) { // return num1 - num2; // } } class B extends Base { // TODO 类 B `无意` 重写了父类 A 方法,造成原有方法发生改变。 // @Override // public int func1(int num1, int num2) { // return num1 + num2; // } @Override public int func1(int num1, int num2) { return num1 + num2; } public int func2(int num1, int num2) { return func1(num1, num2) + 9; } private A a = new A();//组合 //使用 A 方法 public int func3(int num1, int num2) { return this.a.func1(num1, num2); } }
五、开闭原则 OCP(Open Closed
- 1、开闭原则(Open Closed Principle) 是编程中最基础、最重要的设计原则;
- 2、
一个软件实体,比如类,模块和函数应该对提供方扩展开放,对使用方修改关闭。用抽象构建框架,用实现扩展细节;
- 3、当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化;
- 4、编程中遵循其它原则,以及使用设计模式的目的就是遵循开闭原则。
//开闭原则
public class Ocp {
public static void main(String[] args) {
Use use = new Use();
use.drawShape(new Triangle());
use.drawShape(new Circle());
use.drawShape(new OtherGraphics());
}
}
class Use{
public void drawShape(Shape shape){
shape.draw();
}
}
abstract class Shape{
public abstract void draw();
}
class Triangle extends Shape{
@Override
public void draw() {
System.out.println("绘制三角形");
}
}
class Circle extends Shape{
@Override
public void draw() {
System.out.println("绘制圆形");
}
}
class OtherGraphics extends Shape {
@Override
public void draw() {
System.out.println("子类实现具体功能:任何形状");
}
}
六、迪米特法则(Demeter)
-
1、一个对象应该对其他对象保持最少的了解
(最少知道原则 LKP)
。 -
2、类与类关系越密切,耦合度越大。
要求降低类之间耦合,而不是完全解耦。
-
3、迪米特法则(Demeter Principle),即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供public方法,不对外泄露任何信息。
-
4、迪米特法则更简单的定义:只与直接的朋友通信。
-
5、直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖,关联,组合,聚合 等。其中,我们称出现成员变量,方法参数,方法返回值中的类为直接的朋友,而出现在局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量的形式出现在类的内部。
class A{ B b;//全局变量 - 直接朋友 public B m1(){} //方法返回值 - 直接朋友 public void m2(B b){}//方法入参 - 直接朋友 public void m3(){ B b1 = new B();// 局部变量 非直接朋友 } }
public class Demeter { public static void main(String[] args) { SchoolManager schoolManager = new SchoolManager(); schoolManager.printAllEmployee(new CollegeManager()); } } //学院员工类 class CollegeEmployee { private String id; public String getId() { return id; } public void setId(String id) { this.id = id; } } //管理学院员工的管理类: class CollegeManager { //返回学院的所有员工 //TODO CollegeEmployee 直接朋友 public List<CollegeEmployee> getAllEmployee() { List<CollegeEmployee> list = new ArrayList<CollegeEmployee>(); for (int i = 0; i < 10; i++) { //这里我们增加了10 个员工到list , CollegeEmployee emp = new CollegeEmployee(); emp.setId("学院员工id " + i); list.add(emp); } return list; } public void printCollegeEmployee() { List<CollegeEmployee> list1 = this.getAllEmployee(); System.out.println("---学院员工----"); for (CollegeEmployee e : list1) { System.out.println(e.getId()); } } } //学校总部员工类 class SchoolEmployee { private String id; public String getId() { return id; } public void setId(String id) { this.id = id; } } //学校管理类 //TODO 直接朋友 Employee CollegeManager class SchoolManager { //返回学校总部的员工 public List<SchoolEmployee> getAllEmployee() { List<SchoolEmployee> list = new ArrayList<SchoolEmployee>(); for (int i = 0; i < 5; i++) { //这里我们增加了5个员工到list SchoolEmployee emp = new SchoolEmployee(); emp.setId("学校总部员工id= " + i); list.add(emp); } return list; } //该方法完成输出学校总部和学院员工信息(id) void printAllEmployee(CollegeManager sub) { //获取到学院员工 //TODO 非直接朋友 CollegeEmployee 应该提取到 CollegeManager // List<CollegeEmployee> list1 = sub.getAllEmployee(); // System.out.println("---学院员工----"); // for (CollegeEmployee e : list1) { // System.out.println(e.getId()); // } sub.printCollegeEmployee();//只提供方法,不把具体实现放在其他类里面。 //获取到学校总部员工 List<SchoolEmployee> list2 = this.getAllEmployee(); System.out.println("------学校总部员工------"); for (SchoolEmployee e : list2) { System.out.println(e.getId()); } } }
七、合成复用原则(Composite Reuse)
合成复用原则 尽量使用组合/聚合的方式,而不是使用继承。
-
1、找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
-
2、针对接口编程,而不是针对实现编程。
-
3、为了交互对象之间的松耦合设计而努力。
-
public class CompositeReuse { public static void main(String[] args) { System.out.println("------依赖------"); B b = new B(); b.Operation1(new A()); System.out.println("------聚合------"); b.setA(new A()); b.Operation2(); System.out.println("------组合------"); b.Operation3(); } } class A { void Operation1() { System.out.println("A Operation1"); } void Operation2() { System.out.println("A Operation2"); } void Operation3() { System.out.println("A Operation3"); } } //如果只是需要用到 A类的方法,尽量不要使用继承。而是使用,依赖,聚合,组合的方式 class B { void Operation1(A a) {//TODO 依赖 a.Operation1(); a.Operation2(); a.Operation3(); } //============================================================== A a; public void setA(A a) { this.a = a; } void Operation2() {//TODO 聚合 a.Operation1(); a.Operation2(); a.Operation3(); } //============================================================== A a1 = new A(); void Operation3() {//TODO 组合 a1.Operation1(); a1.Operation2(); a1.Operation3(); } }
总结一下,依赖是传入参数,聚合是通过set注入,组合是再类里new一个实例。
2、UML
IDEA PlantUML表示类与类之间的关系的符号
@startuml
Class1 <|-- ClassA:泛化
Class2 <-- ClassB:关联
Class3 *-- ClassC:组合
Class4 o-- ClassD:聚合
Class5 <|.. ClassE:实现
Class6 <.. ClassF:依赖
@enduml
1、依赖(Dependence)
只要是在类中用到了对方,那么他们之间就存在依赖关系。如果没有对方,连编绎都通过不了。
-
类中用到了对方;
-
类的成员属性;
-
方法的返回类型;
-
方法接收的参数类型;
-
方法中使用到。
-
/** * 类中用到了对方; * 类的成员属性; * 方法的返回类型; * 方法接收的参数类型; * 方法中使用到; */ public class Dependence { A a;//TODO 类的成员属性 public A save(B b) {//TODO 方法接收的参数类型 //TODO 方法的返回类型 System.out.println(""); A a = new A();//TODO 方法中使用到 return a; } } class A {} class B {}
2、继承(泛化 Generalization)
public class Generalization extends Base {
@Override
public void get(Object oId) {
}
@Override
public void put(Object oName) {
}
}
abstract class Base {
abstract public void get(Object oId);
abstract public void put(Object oName);
}
3、实现(Realization)
实现关系实际上就是 A类 实现 B接口,依赖关系的特例。
public class Implementation implements Base {
@Override
public void init() {
System.out.println("init");
}
}
interface Base {
void init();
}
4、关联(Association)
类与类之间的关系,依赖关系的特例。
关联具有导航性:即双向关系或单向关系。
public class Person {
private IDCard idCard;
}
class IDCard {
//private Person person;
}
5、聚合(Aggregation)
表示的是整体和部分的关系,整体与部分可以分开,关联关系的特例。
聚合关系是关联关系的特例,所以他具有关联的导航性与多重性。
public class Computer {
private Mouse mouse;
private Keyboard keyboard;
public void setMouse(Mouse mouse) {
this.mouse = mouse;
}
public void setKeyboard(Keyboard keyboard) {
this.keyboard = keyboard;
}
}
class Mouse {}
class Keyboard {}
6、组合(Composite)
整体与部分的关系,但是整体与部分不可以分开,关联关系的特例。
级联删除就是组合关系。
public class Computer {
private CPU cpu = new CPU();
private SSD ssd = new SSD();
}
class CPU {}
class SSD {}
3、单例模式
单例设计模式 —> 创建型模式
单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。
通过单例模式可以保证系统中,应用该模式的类只有一个实例。即一个类只有一个对象实例。
在java语言中,单例带来了两大好处:
(1)对于频繁使用的对象(数据源、Session工厂),可以省略创建对象所花费的时间,这对于重量级的对象而言,是非常可观的一笔系统开销。
(2)由于new操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻GC压力,缩短GC停顿时间。
具体实现
需要:
(1)将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
(2)在该类内部产生一个唯一的实例化对象,并且将其封装为 private static 类型。
(3)定义一个静态方法返回这个唯一对象。
实现一:饿汉式 / 静态常量
- 立即加载就是使用类的时候已经将对象创建完毕(不管以后会不会使用到该实例化对象,先创建了)。常见的实现办法就是直接new实例化。
/**
* 懒汉单例
*/
class Singleton{
//构造器私有化
private Singleton(){
}
//类加载时实例化
private final static Singleton instance = new Singleton();
//对外提供静态方法
public static Singleton getInstance(){
return instance;
}
}
“饿汉模式”的优缺点:
- 优点:实现起来简单,没有多线程同步问题。
- 缺点:当类 Singleton 被加载的时候,会初始化 static 的 instance,静态变量被创建并分配内存空间,从这以后,这个 static 的 instance 对象便一直占着这段内存,可能造成内存浪费(即便你还没有用到这个实例)。当类被卸载时,静态变量被摧毁,并释放所占有内存,在某些特定条件下会耗费内存。
- 如果方法内有其他 static 方法,调用该方法此类也会加载初始
实现二:饿汉式 / 静态代码块
//懒汉静态代码块
class Singleton1{
private static Singleton1 instance;
private Singleton1(){
}
static {
instance = new Singleton1();
}
public static Singleton1 getInstance(){
return instance;
}
}
实现三:懒汉式 / 线程不安全
- 延迟加载就是调用 getInstance() 方法时实例才被创建(先不急着实例化出对象,等要用的时候才创建出来)。常见的实现方法就是在 getInstance() 方法中进行new实例化。
public class LazyLoadingSingletonThreadUnSafe {
public static void main(String[] args) {
Singleton singleton1 = Singleton.getInstance();
Singleton singleton2 = Singleton.getInstance();
System.out.println(singleton1==singleton2);//true
}
}
//懒汉单例 线程不安全
class Singleton{
//将自己实例化对象设置为一个属性,用static
private static Singleton instance;
//构造方法私有化
private Singleton(){}
//静态方法返回实例
public static Singleton getInstance(){
if (instance==null){
//线程在这里被阻塞,则对象没别创建,不安全
instance = new Singleton();
}
return instance;
}
}
懒汉模式”的优缺点:
- 优点:实现起来比较简单,当类 Singleton 被加载的时候,静态变量 static 的 instance 未被创建并分配内存空间,当 getInstance() 方法第一次被调用时,初始化instance变量,并分配内存,因此在某些特定条件下会节约了内存。
- 缺点:在多线程环境中,这种实完现方法是全错误的,不能保证单例的状态。
- 如果方法内有其他 static 方法,调用该方法此类不会加载初始化。
实现四:懒汉式 / 线程安全 Sync
- 静态方法返回该实例,加
Synchronized
关键字实现同步。
class Singleton1{
//将自己实例化对象设置为一个属性,用static
private static Singleton1 instance;
//构造方法私有化
private Singleton1(){}
//静态方法返回实例,方法加synchronized 关键字
public static synchronized Singleton1 getInstance(){
if (instance==null){
instance = new Singleton1();
}
return instance;
}
}
线程安全的“懒汉模式”(加锁)的优缺点:
- 优点:在多线程情形下,保证了“懒汉模式”的线程安全。
- 缺点:在多线程情形下,synchronized方法通常效率低,显然这不是最佳的实现方案。
- 如果方法内有其他static方法,调用该方法此类不会加载初始化。
实现五:DCL双检查锁机制
(DCL:Double Checked Locking)
public class LazyLoadingSingletonDCL {
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
Singleton instance1 = Singleton.getInstance();
System.out.println(instance==instance1);
}
}
class Singleton {
// 将自身实例化对象设置为一个属性,并用 volatile、static 修饰
private volatile static Singleton instance;
//构造器私有化
private Singleton() {
}
// 静态方法返回该实例
public static Singleton getInstance() {
// 第一次检查instance是否被实例化出来
if (instance == null) {
synchronized (Singleton.class) {
// 某个线程取得了类锁,实例化对象前第二次检查 instance 是否已经被实例化
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
方法五算是单例模式的最佳实现方式。内存占用率高,效率高,线程安全,多线程操作原子性。
- 如果方法内有其他 static 方法,调用该方法此类不会加载初始化。
实现六:静态内部类
class Singleton{
private Singleton(){}
public static Singleton getInstance(){
return SingleHolder.INSTANCE;
}
//静态的内部类
private static class SingleHolder{
//加载 Singleton 类时并不会加载内部类
private static final Singleton INSTANCE = new Singleton();
}
}
第一次加载 Singleton 类时并不会加载内部类初始化 Instance,只有第一次调用 getInstance 方法时虚拟机加载 SingletonHolder 并初始化 Instance ,这样不仅能确保线程安全也能保证 Singleton类的唯一性,所以推荐使用静态内部类单例模式。
public class Runtime {
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
//TODO 私有化构造器
private Runtime() {}