读书笔记----软件设计原则、设计模式
| 这个作业属于哪个课程 | https://edu.cnblogs.com/campus/gdgy/2021Softwarecodedevelopmenttechnology |
|---|---|
| 这个作业要求在哪里 | https://edu.cnblogs.com/campus/gdgy/2021Softwarecodedevelopmenttechnology/homework/11833 |
| 这个作业的目标 | 阅读设计模式、设计原则有关的书籍,结合一些图、表工具,用自己的语言对书的主要内容进行归纳总结。 结合自己曾经的软件开发实践,思考用到了哪些原则或模式,谈谈读书的心得体会。 |
1、参考资料
Java设计模式:23种设计模式全面解析(超级详细):http://c.biancheng.net/design_pattern/
2、阅读笔记
2.1、设计原则
在构建程序的过程中遵循设计原则是为了提高软件系统的可维护性和可复用性,增加软件的可扩展性和灵活性,从而提高软件开发效率、节约软件开发成本和维护成本。
这些设计原则分别为:分别为开闭原则、里氏替换原则、依赖倒置原则、单一职责原则、接口隔离原则、迪米特法则和合成复用原则。
设计原则的简单介绍
| 原则 | 简介 |
|---|---|
| 开闭原则 | 软件实体应当对扩展开放,对修改关闭,即当应用的需求改变时,在不修改软件实体的源代码或者二进制代码的前提下,可以扩展模块的功能,使其满足新的需求。 |
| 里氏替换原则 | 继承必须确保超类所拥有的性质在子类中仍然成立,即子类可以扩展父类的功能,但不能改变父类原有的功能。 |
| 依赖倒置原则 | 高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。其核心思想是:要面向接口编程,不要面向实现编程。 |
| 单一职责原则 | 对象不应该承担太多职责,如果一个对象承担了太多的职责,至少存在这两个缺点:一、一个职责的变化可能会削弱或者抑制这个类实现其他职责的能力;二、当客户端需要该对象的某一个职责时,不得不将其他不需要的职责全都包含进来,从而造成冗余代码或代码的浪费。 |
| 接口隔离原则 | 尽量将臃肿庞大的接口拆分成更小的和更具体的接口,让接口中只包含客户感兴趣的方法。要为各个类建立它们需要的专用接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。 |
| 迪米特法则 | 如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。 |
| 合成复用原则 | 在软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。 |
2.2、设计模式
软件设计模式,又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案。也就是说,它是解决特定问题的一系列套路,是前辈们的代码设计经验的总结,具有一定的普遍性,可以反复使用。其目的是为了提高代码的可重用性、代码的可读性和代码的可靠性。
设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。
2.2.1、设计模式的分类
| 范围\目的 | 创建型模式 | 结构型模式 | 行为型模式 |
|---|---|---|---|
| 类模式 | 工厂方法 | (类)适配器 | 模板方法、解释器 |
| 对象模式 | 单例、原型、抽象工厂、建造者 | 代理、(对象)适配器、桥接、装饰、外观、享元、组合 | 策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录 |
(以下仅展示单例模式、原型模式、建造者模式)
2.2.2、创建型模式
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是“将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节,对象的创建由相关的工厂来完成。
创建型模式包含:单例模式、原型模式、简单工厂模式、工厂方法模式、抽象工厂模式、建造者模式
2.2.2.1、单例模式
-
单例模式的定义:指一个类只有一个实例,且该类能自行创建这个实例的一种模式。
-
特点:
-
单例类只有一个实例对象;
-
该单例对象必须由单例类自行创建;
-
单例类对外提供一个访问该单例的全局访问点。
-
-
优点:
-
单例模式可以保证内存里只有一个实例,减少了内存的开销。
-
可以避免对资源的多重占用。
-
单例模式设置全局访问点,可以优化和共享资源的访问。
-
-
缺点:
-
单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则。
-
在并发测试中,单例模式不利于代码调试。在调试过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象。
-
单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则。
-
-
实现:
饿汉式单例(获取对象前,对象就已经创建):
/** * 单例类 */ public class Single { //单例对象 private static final Single example = new Single(); private String name; //只能通过这个接口访问到对象 public static Single getInstance(){ return example; }; //构造器是私有的 private Single(){ } public String getName() { return name; } public void setName(String name) { this.name = name; } }
懒汉式单例(需要获取单例对象才创建单例对象):
/**
* 单例类
*/
public class Single {
//单例对象
private static volatile Single example = null; //保证 example 在所有线程中同步
private String name;
//只能通过这个接口访问到对象
public static synchronized Single getInstance(){ //需要加上同步锁
if (example == null) {
example = new Single();
}
return example;
};
//构造器是私有的,避免类在外部被实例化
private Single(){
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
使用类:
public class Client {
public static void main(String[] args) {
//直接使用new来创建单例对象会报错
//Single example = new Single();
Single example1 = Single.getInstance();
Single example2 = Single.getInstance();
//查看两个变量指向的是不是同一个对象
example1.setName("单例模式");
System.out.println(example2.getName());
System.out.println(example1==example2);
}
}
2.2.2.2、原型模式
-
定义:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。
-
优点:
-
Java 自带的原型模式基于内存二进制流的复制,在性能上比直接 new 一个对象更加优良。
-
可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。
-
-
缺点:
-
需要为每一个类都配置一个 clone 方法
-
clone 方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则。
-
当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深克隆、浅克隆需要运用得当。
-
-
实现:
实现类型
浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
仅展示浅克隆:
/**
* 原型类
* 需要实现java提供的Cloneable接口
*/
public class Realizetype implements Cloneable{
private String name;
@Override
protected Object clone() throws CloneNotSupportedException {
return (Realizetype) super.clone();
}
public Realizetype(String name) {
this.name = name;
}
public Realizetype() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
使用类:
public class Client {
public static void main(String[] args) {
Realizetype r1 = new Realizetype("原型模式");
Realizetype r2 = null;
try {
r2 = (Realizetype) r1.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
if (r2 != null) {
System.out.println(r2.getName());
}
}
}
2.2.2.3、建造者模式
-
定义:指将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示,这样的设计模式被称为建造者模式。它是将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成。它将变与不变相分离,即产品的组成部分是不变的,但每一部分是可以灵活选择的。
-
优点:
- 封装性好,构建和表示分离。
- 扩展性好,各个具体的建造者相互独立,有利于系统的解耦。
- 客户端不必知道产品内部组成的细节,建造者可以对创建过程逐步细化,而不对其它模块产生任何影响,便于控制细节风险。
-
缺点:
- 产品的组成部分必须相同,这限制了其使用范围。
- 如果产品的内部变化复杂,如果产品内部发生变化,则建造者也要同步修改,后期维护成本较大。
-
结构:
建造者模式的主要角色如下。
- 产品角色(Product):它是包含多个组成部件的复杂对象,由具体建造者来创建其各个零部件。
- 抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法 getResult()。
- 具体建造者(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。
- 指挥者(Director):它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息。
结构图如下:
![]()
-
实现:
产品:
public class Product { private String part1; //部分1 private String part2; //部分2 private String part3; //部分3 public String getPart1() { return part1; } public void setPart1(String part1) { this.part1 = part1; } public String getPart2() { return part2; } public void setPart2(String part2) { this.part2 = part2; } public String getPart3() { return part3; } public void setPart3(String part3) { this.part3 = part3; } @Override public String toString() { return "Product{" + "part1='" + part1 + '\'' + ", part2='" + part2 + '\'' + ", part3='" + part3 + '\'' + '}'; } }
抽象建造者:
//抽象建造者
public interface Builder {
//建造部分1
Builder buildPart1(String part1);
//建造部分2
Builder buildPart2(String part2);
//建造部分3
Builder buildPart3(String part3);
//最终完成建造
Product build();
}
具体建造者:
/**
* 具体建造者
*/
public class BuilderImpl implements Builder{
private Product product = new Product();
@Override
public BuilderImpl buildPart1(String part1) {
product.setPart1(part1);
return this;
}
@Override
public BuilderImpl buildPart2(String part2) {
product.setPart2(part2);
return this;
}
@Override
public BuilderImpl buildPart3(String part3) {
product.setPart3(part3);
return this;
}
@Override
public Product build() {
return product;
}
}
指挥者:
/**
* 指挥者
*/
public class Director {
private Builder builder;
public Director(Builder builder){
this.builder = builder;
}
public Director(){
this.builder = new BuilderImpl();
}
public Product construct(){
return builder.buildPart1("地上跑").buildPart2("天上飞").buildPart3("水里游").build();
}
}
客户:
public class Client {
public static void main(String[] args) {
Product product = new Director().construct();
System.out.println(product);
}
}
3、心得体会
合理使用代理模式,能够在不改动原有代码的同时增强原有代码的功能。
例如下面这个类实现了TowSum接口,它的towSum方法用于在数组nums中寻找相加之和等于target的两个元素。
public class TwoSumImpl implements TwoSum {
@Override
public int[] twoSum(int[] nums, int target) {
int[] result = new int[2];
for(int i = 0; i < nums.length; i++){
for(int j = i+1; j < nums.length; j++){
if(nums[i] + nums[j] == target){
result[0] = i;
result[1] = j;
return result;
}
}
}
return null;
}
}
使用类:
public class Client {
public static void main(String[] args) {
int[] param1 = {1,2,1,1,3,6,8};
int param2 = 3;
System.out.println(new TwoSumImpl().towSum(param1,param2));
}
}
TowSum接口:
public interface TwoSum{
int[] twoSum(int[] nums, int target);
}
如果仔细观察就会发现TwoSumImpl类的twoSum方法并没有对传入的参数进行合法性判断。如果想要改进这个方法,而不对原有代码进行改动,可以使用代理模式。
创建一个代理类如下
public class TwoSumImplProxy implements TwoSum {
@Override
public int[] twoSum(int[] nums, int target) {
if(nums == null || num.length <= 0){
return null;
}
return new TwoSum().towSum(nums,target);
}
}
使用类:
public class Client {
public static void main(String[] args) {
int[] param1 = {1,2,1,1,3,6,8};
int param2 = 3;
System.out.println(new TwoSumImplProxy().towSum(param1,param2));
}
}
使用代理模式前的:

使用代理模式后:

这样一来,即增强了原有代码的功能,有没有对原有代码进行修改,符合开闭原则。
4、后台博文编辑


浙公网安备 33010602011771号