Title:Java设计模式学习 |
---|
Author:ychhh_ |
设计模式概述
设计模式目的
-
编写过程中,程序员面临着耦合性,内聚性以及可维护性,可扩展性,重用性,灵活性等多方面的挑战,设计模式是为了让程序具有更好:
- 代码重用性
- 可读性
- 可靠性
- 是程序具有低耦合性,高内聚性
-
设计模式七大原则:
- 单一职责原则
- 接口隔离原则
- 依赖倒转原则(面向接口编程)
- 里氏替换原则
- 开闭原则
- 迪米特法则
- 合成复用原则
七大原则
单一职责原则
- 基本介绍:对类来说,一个单一类只负责一个职责。若一个类负责多个值则,如:类A拥有这则1和值则2,当值则1发生改变而影响类A,则可能导致值则2的错误,所以需要将类A颗粒化为类A1和类A2
- 单一职责注意事项:
- 降低类的复杂度,一个类只负责一个职责
- 提高类的可读性,可维护性
- 降低变更引起的风险
- 通常情况下需要遵守单一职责的原则,但在类的方法特别少的情况下可以不遵守单一职责
- Attention:
- 对于if…else语句需要谨慎使用,if…else使得代码的耦合程度太高,在必须使用if…else…时考虑使用构造方法或构造类的形式来代替if…else
接口隔离原则
-
基本介绍:
- 客户端不应该依赖不需要的接口,即一个类对另一个类的依赖需要建立在最小耦合的接口之上
-
解决方法:若发生了接口的冗余,则通过接口的分解方式,将一个接口分解为多个子接口
依赖倒转原则(面向接口编程)
- 基本介绍:
- 高层模块不应该依赖于低层模块,二者都应该依赖于其抽象
- 抽象不应该依赖细节,细节应该依赖于抽象
- 依赖倒转即面向接口编程
- 注意事项:
- 底层模块尽量有抽象类或接口或两者都有,这样使得程序的稳定性更好
- 变量的声明类型尽量是抽象类或接口,这样使得变量引用和实际对象间有一层缓冲层,有利于程序的扩展和优化
- 继承时遵循里氏替换原则
里氏替换原则
-
基本介绍:
如果对每个类型为T1的对象o1,有每个类型为T2的对象o2,若将程序所有为o1的地方改为o2,则程序不发生改动,则T2时T1的子类。即引用基类的地方都能透明的使用子类
- 在使用继承时子类尽量不要重写父类方法
- 继承使两个类的耦合程度增强了,在适当的情况下可以通过聚合,组合,依赖来解决
-
对于继承的解耦合:
- 将原来的继承关系进行接触
- 创建一个公共的Base类,最抽象的类,使原先具有继承关系的两个类都来继承这个类
- 在其中一个类里(一般为当初的子类),创建(当初父类)变量
- 使用该对象的方法
开闭原则
-
基本介绍:
- 对扩展(提供者)开放,对修改(使用者)封闭。用抽象扩展框架,用实现修改细节
- 当软件的需求发生变化时尽量通过软件的实体行为来发生变化,而不是通过修改已有的代码来实现
- 编程中遵循的其他原则,以及设计模式的目的就是遵循开闭原则
-
解决方法:
- 通过实现接口和抽象类来将实体进行抽离
- 在实现的接口的子类或实现抽象类的子类中进行细节化,从而降低了耦合性
迪米特法则
-
基本介绍:
-
一个类应对其他类保持最小的了解
-
类与类之间的关系越密切,其耦合度越大
-
迪米特法则:
即最少知到原则,一个类对自己依赖的类知到越少越好。也就是说,不管被依赖的类有多复杂,都尽量封装自己类内部,对外只提供public方法接口
- 直接朋友:在类 的内部直接调用、声明、使用的类
- 简介朋友:在方法的内部声明的局部变量(此时违反了迪米特法则)
-
-
个人理解:
- 即设计需要按照一定的拓扑关系,不能越级调用,需要按照流程来执行想要的功能
-
注意事项:
- 迪米特法则只要求降低耦合而不是完全没有耦合
合成复用原则
- 基本介绍:能够使用合成/聚合的方式就不要使用继承
- 注意事项:
- 找出可能变化之处,并将其独立出来
- 针对接口编程,而不是针对实现编程
- 为了交互对象而实现松耦合
UML类图
-
类和类之间的关系:依赖,泛化,实现,关联,聚合,组合
-
依赖:
只要是在类中用到了对方就存在依赖关系(最基本的类之间的关系,如果没有这个类,连编译都通过不了)
-
泛化(继承):
依赖关系的特例
-
实现:
实现接口,实现关系也为依赖关系
-
关联:
- 表示类与类之间的特例也为依赖关系
- 关联具有导航性,即一对一或是一对多
一对一:
classDiagram class Person{ IDCard card; }
双向一对一:
-
聚合:
各部分成员之间可以分离,既可以独立存在
-
组合:
各部分成员之间不可以分离,即不可以独立存在,必须共生共灭
-
设计模式
基本概念
- 设计模式分为三类,共23种:
- 创建型模式:
- 单例模式
- 抽象工厂模式
- 原型模式
- 建造者模式
- 工厂模式
- 结构型模式:
- 适配器模式
- 桥接模式
- 装饰模式
- 组合模式
- 外观模式
- 享元模式
- 代理模式
- 行为型模式:
- 模板方法模式
- 命令模式
- 访问者模式
- 迭代器模式
- 观察者模式
- 中介者模式
- 备忘录模式
- 解释器模式
- 状态模式
- 策略模式
- 职责链模式
- 创建型模式:
单例模式
- 概念:单例模式即在软件系统中,对某个类只能存在一个对象,并且该类只提供一个取个该实例的方法(静态方法)
饿汉式
-
静态常量写法
class Hungry1{ // 隐藏构造器,使得类外无法创建对象 private Hungry1() { } // 创建静态对象 static final private Hungry1 h1 = new Hungry1(); // 提供静态对象接口 static Hungry1 getHungrt() { return h1; } }
- 优点:在类装载的时候就完成了实例化,避免了线程同步问题
- 缺点:在类装载的过程中就完成了实例化,没有达到lazyloading的效果。如果从未使用过该实例,则会造成内存的浪费。这种基于ClassLoader的机制避免了多线程问题,但导致类加载的方式有很多,,因此不能确定其他方式不会造成类的加载,从而无法达到lazyloading
-
静态代码块写法
class Hungry1{ private Hungry1() { } static final private Hungry1 h1; static { h1 = new Hungry1(); } static Hungry1 getHungrt() { return h1; } }
- 优缺点同上
懒汉式
-
线程不安全方式
class Hungry2{ private Hungry2() { } static private Hungry2 h2; static Hungry2 getHungry() { if(h2 == null) { h2 = new Hungry2(); return h2; } else return h2; } }
-
优点:起到了lazyloading的效果
-
缺点:只能在单线程下使用,但是由于存在
if(h2 == null) { h2 = new Hungry2(); return h2; }
语句,所以存在线程安全问题,不可以在多线程并发的执行(除非专门控制并发)
-
注意事项:在开发过程中尽量不要使用该方式实现单例模式
-
2. 线程安全式(同步方法方式)
class Hungry2{
private Hungry2() {
}
static private Hungry2 h2;
public static synchronized Hungry2 getHungry() {
if(h2 == null) {
h2 = new Hungry2();
return h2;
}
else return h2;
}
}
// 添加syn关键字,保证线程安全
- 优点:保证了线程的同步,解决了线程不安全的问题
- 缺点:使程序的效率变低,需要互斥的执行该函数
-
线程安全式(同步代码块形式)
错误方式,不存在!!!!
双重检查
class Hungry3{
private Hungry3() {
}
static private volatile Hungry3 h3;
public static Hungry3 getInstance() {
if(h3 == null) {
synchronized (Hungry3.class) {
if(h3 == null)h3 = new Hungry3();
}
}
return h3;
}
}
- 优点:Double-Check概念是多线程经常用到的单例方法,进行了两次的null值判断且使用了syn关键词进行同步,既保证了线程安全又保证了高效的处理和懒加载
静态内部类
public class StaticInnerClass {
@Test
public void test() {
}
}
class Static{
private Static() {
}
private static class StaticInner{
private static Static instance = new Static();
}
public static Static getInstance() {
return StaticInner.instance;
}
}
- 由于静态内部类的特殊性,在外部类装载的时候内部类不会进行装载,因此保证了懒加载,又因为内部类只会装载一次,因此保证了线程的安全
枚举类
package SingleInstance;
import org.junit.jupiter.api.Test;
public class EnumClass {
@Test
public void test() {
Enum e1 = Enum.Instance;
Enum e2 = Enum.Instance;
System.out.println(e1 == e2);
}
}
enum Enum{
Instance();
private Enum() {
}
}
- 枚举类的机制保证了线程的安全
单例模式注意
-
单例模式保证了系统内存内只有一个对象,对于需要频繁创建和销毁的对象,使用单例模式可以提高系统的效率
-
想要实例化一个单例时,使用的是相应的获取方法而不是使用new
-
单例模式使用的场景:
-
需要频繁创建和销毁对象的场景
-
创建对象耗时或耗费资源过多但又经常用到的对象
若能确保单线程的情况下:饿汉式(静态常量、静态代码块)
多线程情况下:双重检查,枚举类,静态内部类
-
工厂模式
简单工厂模式
package Factory.SimpleFactory;
public class Factory {
public Pizza getPizza(String name) {
Pizza pizza = null;
if(name.equals("BJ") || name.equals("TJ"))
pizza = new Pizza(name);
return pizza;
}
}
package Factory.SimpleFactory;
import org.junit.jupiter.api.Test;
public class OrderPizza {
private Factory factory = new Factory();
private Pizza pizza = null;
@Test
public void getPizza() {
String name = "BJ";
pizza = factory.getPizza(name);
if(pizza == null)
System.out.println("no");
else
System.out.println("yes");
}
}
package Factory.SimpleFactory;
public class Pizza {
public Pizza(String name) {
this.name = name;
}
String name;
}
- 个人理解:
- 简单工厂模式为对于种类较少的多个同种基类的创建,使用一个类进行菜单的作用,利用菜单进行分发,使用工厂类进行创建
文章评论