第3章 设计原则——单一职责原则
就一个类而言,应该仅有一个引起它变化的原因.。
如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设计,当变化发生时,设计会遭受到意想不到的破坏。
需要将相同的职责放到一起,不同的职责分开到不同的类的实现中去。具体来说,对于一个类,如果能想到多于一个的动机去改变一个类,那么该类具有多于一个的职责,应该考虑将类的职责分解。
个人理解就是只干一件事!
第4章 设计原则——开闭原则
开闭原则,The Open-Closed Principle,简称 OCP,是指软件实体(类、模块、函数等)应该可以扩展,但是不可以修改。即对于扩展是开放的,对于更改是封闭的。通俗来说就是对于要增加的新功能或要调整的改动,尽量扩展新代码而不是修改已有代码。
第5章 设计原则——依赖倒置原则
依赖倒置原则,Dependence Inversion Principle,简称 DIP,是指程序不应该依赖细节,细节应该依赖于抽象。简单来说,就是要针对接口编程,不要针对实现编程。
刚开始的计算机是自成体系的,虽然都是采用同样的设计架构和结构,但组件之间的连接方式不同。如果用 A 公司的电脑,硬盘坏了后只能用 A 公司提供的硬盘。这是一种紧耦合的表现,每个组件将其内部实现暴露给外部对接。
后来几家大公司统一了标准,约定好组件之间连接的标准,标准后面具体怎么做,由相应公司自己负责。这样的结果是,我们既可以使用 A 公司的硬盘,也可以使用 B 公司的硬盘。不光如此,不同大小(如 500G 和 200G)、不同结构(如固态硬盘和机械硬盘)的硬盘也可以互换。真正实现了可拔插、易拔插。
除了硬盘,其他如 CPU、内存、外设设备等各种设备组件也都实现了标准化接口。所有的对接都发生在接口层面,不需要关心具体的实现细节。
第5章 设计原则——里氏替换原则
里氏替换原则,Liskov Substituion Principle,简称 LSP,一个软件实体如果使用的是一个父类的话,一定适用于其子类,而且它察觉不出父类和子类的区别。也就是说,在软件里面,把父类都替换成它的子类,程序的行为没有变化。简单来说,子类型必须能够替换掉它们的父类型。
使用动机
父类能够真正复用(继承),子类也能够在父类的基础上增加新的行为。
如何使用
- 父类一般使用抽象类或接口。
- 抽象类定义公共对象和状态;接口定义公共行为。
- 子类通过继承父类和接口进行扩展。
使用示例
以企鹅和鸟为例。
假设鸟是父类,有下蛋和飞翔两个方法。企鹅作为鸟如果继承了父类,就会出现问题,因为企鹅虽然有翅膀但不会飞。所以,这样设计父类和子类是不合理的,它违反了上面提到的原则,企鹅作为子类无法替换父类。
合理的做法是将飞翔的行为抽象为接口,父类鸟描述状态和公共方法(比如吃),然后会飞的子类再去实现飞翔接口,不会飞的就不用管了。
======================================================
第1章 工厂模式
package com.miniai;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
try {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入数字A:");
Double numberA = Double.parseDouble(scanner.nextLine());
System.out.println("请选择运算符号(+、-、*、/):");
String strOperate = scanner.nextLine();
System.out.println("请输入数字B:");
Double numberB = Double.parseDouble(scanner.nextLine());
double result = 0d;
switch (strOperate){
case "+":
result = numberA + numberB;
break;
case "-":
result = numberA - numberB;
break;
case "*":
result = numberA * numberB;
break;
case "/":
result = numberA / numberB;
break;
}
System.out.println("结果是:" + result);
}
catch (Exception e){
System.out.println("输入有错:" + e.toString());
}
}
}
计算器这个程序,先要求输入两个数和运算符号,然后根据运算符号判断选择如何运算,得到结果,这本身没有错,但这样的思维却使得我们的程序只为满足实现当前的需求,程序不容易维护,不容易扩展,更不容易复用,从而达不到高质量代码的要求。
1.8 业务的封装
package com.miniai;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
try {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入数字A:");
Double numberA = Double.parseDouble(scanner.nextLine());
System.out.println("请选择运算符号(+、-、*、/):");
String strOperate = scanner.nextLine();
System.out.println("请输入数字B:");
Double numberB = Double.parseDouble(scanner.nextLine());
// double result = 0d;
// switch (strOperate){
// case "+":
// result = numberA + numberB;
// break;
// case "-":
// result = numberA - numberB;
// break;
// case "*":
// result = numberA * numberB;
// break;
// case "/":
// result = numberA / numberB;
// break;
// }
double result = Operation.getResult(numberA,numberB,strOperate);
System.out.println("结果是:" + result);
}
catch (Exception e){
System.out.println("输入有错:" + e.toString());
}
}
}
package com.miniai;
public class Operation {
public static double getResult(double numberA,double numberB,String operate){
double result = 0d;
switch (operate){
case "+":
result = numberA + numberB;
break;
case "-":
result = numberA - numberB;
break;
case "*":
result = numberA * numberB;
break;
case "/":
result = numberA / numberB;
break;
}
return result;
}
}
1.9 松耦合vs紧耦合
但是上面的写法容易让原有运行良好的功能代码产生了很大变化。
需要把Operation分成四个类:
package com.miniai;
public abstract class Operation {
public static double getResult(double numberA,double numberB){
return 0d;
}
}
package com.miniai;
public class Add extends Operation{
public double getResult(double numberA,double numberB){
return numberA + numberB;
}
}
package com.miniai;
public class Sub extends Operation{
public double getResult(double numberA,double numberB){
return numberA - numberB;
}
}
package com.miniai;
public class Mul extends Operation{
public double getResult(double numberA,double numberB){
return numberA * numberB;
}
}
package com.miniai;
public class Div extends Operation{
public double getResult(double numberA,double numberB){
if(numberB == 0){
System.out.println("除数不能为0!");
throw new ArithmeticException();
}
return numberA / numberB;
}
}
首先是一个运算抽象类,它有一个方法getResult(numberA,numberB),用于得到结果,然后我把加减乘除都写成了运算类的子类,继承它后,重写了getResult()方法,这样如果要修改任何一个算法,就不需要提供其他算法的代码了。但问题来了,我如何让计算器知道我是希望用哪一个算法呢?
1.10 简单的工厂模式
新建一个操作工厂类:
package com.miniai;
public class OperationFactory {
public static Operation createOperate(String operate){
Operation oper = null;
switch (operate){
case "+":
oper = new Add();
break;
case "-":
oper = new Sub();
break;
case "*":
oper = new Mul();
break;
case "/":
oper = new Div();
break;
}
return oper;
}
}
Main函数改成:
package com.miniai;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
try {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入数字A:");
Double numberA = Double.parseDouble(scanner.nextLine());
System.out.println("请选择运算符号(+、-、*、/):");
String strOperate = scanner.nextLine();
System.out.println("请输入数字B:");
Double numberB = Double.parseDouble(scanner.nextLine());
Operation oper = OperationFactory.createOperate(strOperate);
double result = oper.getResult(numberA,numberB);
System.out.println("结果是:" + result);
}
catch (Exception e){
System.out.println("输入有错:" + e.toString());
}
}
}
1.11 UML类图
1.1 引入工厂模式
首先根据下面的UML类图去做工厂模式基础上:
Main.java
package com.miniai;
import com.miniai.factory.CashFactory;
import com.miniai.factory.OperationFactory;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
// 2. 商品 工厂模式
double price = 0d;
int num = 0;
double totalPrice = 0d;
double total = 0d;
int discount = 0;
Scanner scanner = new Scanner(System.in);
do{
System.out.println("请输入商品销售模式 1.原价 2.八折 3.七折");
discount = Integer.parseInt(scanner.nextLine());
System.out.println("请输入商品单价:");
price = Double.parseDouble(scanner.nextLine());
System.out.println("请输入商品数量:");
num = Integer.parseInt(scanner.nextLine());
System.out.println();
if(price >0 && num>0){
//这里就可以去写一个工厂
CashSuper oper = CashFactory.createCrash(discount);
totalPrice = oper.getCrash(price,num);
total = total + totalPrice;
System.out.println();
System.out.println("单价:" + price + "元 数量:" + num + " 合计:" + totalPrice + "元");
System.out.println();
System.out.println("总计:" + total + "元");
System.out.println();
}
}while(price>0 && num>0);
}
}
CashSuper.java
public abstract class CashSuper {
public abstract double getCrash(double price,int num);
}
CashFactory.java
public class CashFactory {
public static CashSuper createCrash(int operate){
CashSuper oper = null;
switch (operate){
case 1:
oper = new CashNormal();
break;
case 2:
oper = new CashRebate(0.8d);
break;
case 3:
oper = new CashRebate(0.7d);
case 4:
oper = new CashReturn(300d,100d);
break;
}
return oper;
}
}
CashNormal.java
public class CashNormal extends CashSuper {
public double getCrash(double price,int num){
return price * num;
}
}
CashRebate.java
public class CashRebate extends CashSuper {
private double moneyRebate = 1d;
public CashRebate(double moneyRebate){
this.moneyRebate = moneyRebate;
}
public double getCrash(double price,int num){
return price * num * this.moneyRebate;
}
}
CashReturn.java
public class CashReturn extends CashSuper {
private double moneyReturn = 0d;
private double moneyCondition = 0d;
public CashReturn(double moneyCondition,double moneyReturn){
this.moneyReturn = moneyReturn;
this.moneyCondition = moneyCondition;
}
public double getCrash(double price,int num){
double result = price * num;
if (moneyCondition>0 && result >= moneyCondition){
result = result - Math.floor(result/moneyCondition) * moneyReturn;
}
return result;
}
}
工厂模式只是解决对象的创建问题,而且由于工厂本身包括所有的收费方式,商场是可能经常性地更改打折额度和返利额度,每次维护或扩展收费方式都要改动这个工厂,以致代码需重新编译部署,这真的是很糟糕的处理方式。
1.2 策略模式
策略模式:它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。
在前面的例子中,仅仅需要新增CashContext类
public class CashContext {
private CashSuper cs;
public CashContext(int cashSuper){
switch (cashSuper){
case 1:
this.cs = new CashNormal();
break;
case 2:
this.cs = new CashRebate(0.8d);
break;
case 3:
this.cs = new CashRebate(0.7d);
case 4:
this.cs = new CashReturn(300d,100d);
break;
}
}
public double getResult(double price,int num){
return this.cs.getCrash(price,num);
}
}
客户端代码:
工厂模式我需要让客户端认识两个类,CashSuper 和 CashFactory,而策略模式与简单工厂结合的用法,客户端就只需要认识一个类CashContext 就可以了,耦合更加降低。
我们在客户端实例化的是CashContext的对象,调用的是CashContext的方法GetResult,这使得具体的收费算法彻底地与客户端分离。
策略模式的Strategy类层次为Context定义了一系列的可供重用的算法或行为。继承有助于析取出这些算法中的公共功能。对于打折、返利或者其他的算法,其实都是对实际商品收费的一种计算方式,通过继承,可以得到它们的公共功能。公共的功能就是获得计算费用的结果GetResult,这使得算法间有了抽象的父类CashSuper。
第6章 装饰模式
装饰模式 (Decorator) ,动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。
Component是定义一个对象接口,可以给这些对象动态地添加职责ConcreteComponent是定义了一个具体的对象,也可以给这个对象添加一些职责。
Decorator,装饰抽象类,继承了Component,从外类来扩展Component类的功能,但对于Component来说,是无须知道Decorator的存在的。至于ConcreteDecorator就是具体的装饰对象,起到给Component添加职责的功能[DPE]。
装饰模式是利用SetComponent来对对象进行包装的。这样每个装饰对象的实现就和如何使用这个对象分离开了,每个装饰对象只关心自己的功能,不需要关心如何被添加到对象链当中。
装饰模式是为已有功能动态地添加更多功能的一种方式。
当系统需要新功能的时候,是向旧的类中添加新的代码。这些新加的代码通常装饰了原有类的核心职责或主要行为,
而这些新加入的东西仅仅是为了满足一些只在某种特定情况下才会执行的特殊行为的需要。而装饰模式却提供了一个非常好的解决方案,它把每个要装饰的功能放在单独的类中,并让这个类包装它所要装饰的对象,因此,当需要执行特殊行为时,客户代码就可以在运行时根据需要有选择地、按顺序地使用装饰功能包装对象了.
装饰模式的优点是,把类中的装饰功能从类中搬移去除,这样可以简化原有的类。
有效地把类的核心职责和装饰功能区分开了。而且可以去除相关类中重复的装饰逻辑。
第7章 代理模式
代理模式(Proxy),为其他对象提供一种代理以控制对这个对象的访问。
IGiveGift类,定义了 Persuit 和Proxy的共用接口,这样就在任何使用Persuit 的地方都可以使用Proxy。
public interface IGiveGift {
void giveDolls();
void giveFlowers();
void giveChocolate();
}
SchoolGirl类
public class SchoolGirl {
private String name;
public SchoolGirl(){
}
public SchoolGirl(String name){
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Persuit类,定义Proxy所代表的真实实体
//追求者
public class Persuit implements IGiveGift{
private SchoolGirl mm;
public Persuit(SchoolGirl mm){
this.mm = mm;
}
@Override
public void giveDolls() {
System.out.println(this.mm.getName() + ",你好!送你洋娃娃");
}
@Override
public void giveFlowers() {
System.out.println(this.mm.getName() + ",你好!送你鲜花");
}
@Override
public void giveChocolate() {
System.out.println(this.mm.getName() + ",你好!送你巧克力");
}
}
Proxy类,定义Proxy所代表的真实实体
public class Proxy implements IGiveGift{
private Persuit gg;
public Proxy(SchoolGirl mm){
this.gg = new Persuit(mm);
}
@Override
public void giveDolls() {
this.gg.giveDolls();
}
@Override
public void giveFlowers() {
this.gg.giveFlowers();
}
@Override
public void giveChocolate() {
this.gg.giveChocolate();
}
}
客户端代码
public class ProcyClient {
public static void main(String[] args) {
SchoolGirl girlLjj = new SchoolGirl();
girlLjj.setName("李娇娇");
Proxy boyDl = new Proxy(girlLjj);
boyDl.giveDolls();
boyDl.giveFlowers();
boyDl.giveChocolate();
}
}
代理模式的应用
1. <u>远程代理</u>,也就是为一个对象在不同的地址空间提供局部代表。这样可以隐藏一个对象存在于不同地址空间的事实。
当我在项目中加入一个WebService,此时会在项目中生成一个wsd1文件和一些相关文件,其实它们就是代理,这就使得客户端程序调用代理就可以解决远程访问的问题。
2. <u>虚拟代理</u>,是根据需要创建开销很大的对象。通过它来存放实例化需要很长时间的真实对象 。这样就可以达到性能的最优化,比如说你打开一个很大的HTML网页时,里面可能有很多的文字和图片,但你还是可以很快打开它,此时你所看到的是所有的文字,但图片却是一张一张地下载后才能看到。那些未打开的图片框,就是通过虚拟代理来替代了真实的图片,此时代理存储了真实图片的路径和尺寸。
3. <u>安全代理</u>,用来控制真实对象访问时的权限。一般用于对象应该有不同的访问权限的时候。
4. <u>智能指引</u>,是指当调用真实的对象时,代理处理另外一些事。
代理模式其实就是在访问对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。代理就是真实对象的代表。