一. 设计模式概念
对接口编程而不是对实现编程;优先使用对象组合而不是继承
二. 设计模式总览
1. 创建型模式(Creational Patterns):(5)
单例(Singleton)模式 原型(Prototype)模式 工厂方法(FactoryMethod)模式
抽象工厂(AbstractFactory)模式 建造者(Builder)模式
2. 结构型模式(Structural Patterns): (7)
代理(Proxy)模式 适配器(Adapter)模式 桥接(Bridge)模式
装饰(Decorator)模式 外观(Facade)模式 享元(Flyweight)模式
组合(Composite)模式 过滤器模式(Filter Pattern)
3. 行为型模式(Behavioral Patterns): (11)
模板方法(Template Method)模式 策略(Strategy)模式 命令(Command)模式
职责链(Chain of Responsibility)模式 状态(State)模式 观察者(Observer)模式
中介者(Mediator)模式 迭代器(Iterator)模式 访问者(Visitor)模式
备忘录(Memento)模式 解释器(Interpreter)模式
4. 划分依据
在编写代码时,其实就是对组件(类、接口等)的一系列操作。所以设计模式可以根据组件的生命周期来划分,在组件定义环节如何构建组件就会用到结构型模型;在组件创建环节使用创建型模式;在组件的使用环节可用行为型模式。当然,每个环节并不是独立的,所以各个环节所应用的模式都应该互相考量。
三. 设计模式七大原则
设计模式常用的七大原则有:
1)单一职责原则 2)接口隔离原则 3)依赖倒转(倒置)原则 4)里氏替换原则
5)开闭原则 6)迪米特法则 7)合成复用原则
1. 单一职责原则
一个类应该有且仅有一个引起它变化的原因,否则类应该被拆分。即每个类只负责自己的事情,而不是变成万能
代码示例
以交通工具的使用为例:
方案1,违法单一职责原则
public class SingleResponsibility1 {
public static void main(String[] args) {
Vehicle vehicle = new Vehicle();
vehicle.run("汽车");
vehicle.run("摩托");
vehicle.run("飞机");
}
}
//方案1,违法单一职责原则
class Vehicle{
public void run(String vehicle){
System.out.println(vehicle+"在公路上行驶");
}
}
方案2,遵守单一职责原则,但是改动很大,要将类分解,且修改客户端
public class SingleResponsibility2 {
public static void main(String[] args) {
RoadVehicle roadVehice = new RoadVehicle();
roadVehice.run("汽车");
roadVehice.run("摩托");
AirVehicle airVehicle = new AirVehicle();
airVehicle.run("飞机");
}
}
//方案2,遵守单一职责原则,但是改动很大,要将类分解,且修改客户端
class RoadVehicle{
public void run(String vehicle){
System.out.println(vehicle+"在公路上行驶");
}
}
class AirVehicle{
public void run(String vehicle){
System.out.println(vehicle+"在天空上行驶");
}
}
方案3,没有对原来类对大的修改,只是增加方法,虽然没有在类这个级别上遵守单一职责原则,但在方法级别上仍然遵守单一职责原则
public class SingleResponsibility3 {
public static void main(String[] args) {
Vehicle2 vehicle = new Vehicle2();
vehicle.run("汽车");
vehicle.run("摩托");
vehicle.runAir("飞机");
}
}
//方案3,没有对原来类对大的修改,只是增加方法
// 虽然没有在类这个级别上遵守单一职责原则,但在方法级别上仍然遵守单一职责原则
class Vehicle2{
public void run(String vehicle){
System.out.println(vehicle+"在公路上行驶");
}
public void runAir(String vehicle){
System.out.println(vehicle+"在天空上行驶");
}
}
2. 接口隔离原则
一个类对另一个类的依赖应该建立在最小的接口上。即各个类建立自己的专用接口,而不是建立万能接口
代码示例
实现下列UML: 类B实现五个方法;类D实现五个方法;类A通过接口Interface1 依赖(使用) B类,但是只会用到1,2,3方法;类C通过接口Interface1 依赖(使用) D类,但是只会用到1,4,5方法
不足:类A通过接口Interface1依赖类B,类C通过接口Interface1依赖类D,如果接口Interface1对于类A和类C来说不是最小接口,那么类B和类D必须去实现他们不需要的方法。
public class Segregation1 {
public static void main(String[] args) {
Interface1 B = new B();
A a = new A();
a.depend1(B);
a.depend2(B);
a.depend3(B);
}
}
//接口定义五个方法
interface Interface1{
void operation1();
void operation2();
void operation3();
void operation4();
void operation5();
}
//类B实现五个方法
class B implements Interface1{
@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");
}
@Override
public void operation4() {
System.out.println("B实现operation4");
}
@Override
public void operation5() {
System.out.println("B实现operation5");
}
}
//类D实现五个接口
class D implements Interface1{
@Override
public void operation1() {
}
@Override
public void operation2() {
}
@Override
public void operation3() {
}
@Override
public void operation4() {
}
@Override
public void operation5() {
}
}
//类A通过接口Interface1 依赖(使用) B类,但是只会用到1,2,3方法
class A{
public void depend1(Interface1 i){
i.operation1();
}
public void depend2(Interface1 i){
i.operation2();
}
public void depend3(Interface1 i){
i.operation3();
}
}
//类C通过接口Interface1 依赖(使用) D类,但是只会用到1,4,5方法
class C{
public void depend1(Interface1 i){
i.operation1();
}
public void depend4(Interface1 i){
i.operation4();
}
public void depend5(Interface1 i){
i.operation5();
}
}
改进:使用接口隔离原则
public class SegregationImprove {
public static void main(String[] args) {
A a = new A();
a.depend1(new B());
a.depend2(new B());
a.depend3(new B());
}
}
//接口1
interface Interface1{
void operation1();
}
//接口2
interface Interface2{
void operation2();
void operation3();
}
//接口3
interface Interface3{
void operation4();
void operation5();
}
//类B实现
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");
}
}
//类D实现
class D implements Interface1,Interface3{
@Override
public void operation1() {
}
@Override
public void operation4() {
}
@Override
public void operation5() {
}
}
class A{
public void depend1(Interface1 i){
i.operation1();
}
public void depend2(Interface2 i){
i.operation2();
}
public void depend3(Interface2 i){
i.operation3();
}
}
class C{
public void depend1(Interface1 i){
i.operation1();
}
public void depend4(Interface3 i){
i.operation4();
}
public void depend5(Interface3 i){
i.operation5();
}
}
3. 依赖倒转(倒置)原则
高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。即面向接口编程,而不是面向实现类
代码示例
完成Person接受消息功能:方式1,简单比较容易想到,但是如果我们获取的对象是微信等,就要新增类和相应的方法
public class DependenceReversal1 {
public static void main(String[] args) {
Person person = new Person();
person.receive(new Email());
person.receive02(new Wechat());
}
}
class Email{
public String getInfo(){
return "电子邮件信息:hello";
}
}
//增加微信
class Wechat{
public String getInfo(){
return "微信信息:hello";
}
}
//方式1,简单比较容易想到,但是如果我们获取的对象是微信等,就要新增类和相应的方法
class Person{
public void receive(Email email){
System.out.println(email.getInfo());
}
public void receive02(Wechat wechat){
System.out.println(wechat.getInfo());
}
}
改进:引入一个抽象的接口IReceiver,表示接收者,这样Person类与接口IReceiver发生依赖。因为Email,微信等都属于接收者的范围,各自实现IReceiver,符合依赖倒转原则,无需新增方法
public class DependenceReversal {
public static void main(String[] args) {
//客户端无需改变
Person person = new Person();
person.receive(new Email());
//新增微信
person.receive(new Wechat());
}
}
//定义接口
interface IReceiver{
public String getInfo();
}
class Email implements IReceiver{
public String getInfo(){
return "电子邮件信息:hello";
}
}
//增加微信
class Wechat implements IReceiver{
public String getInfo(){
return "微信信息:hello";
}
}
//方式2,对接口的依赖
class Person{
public void receive(IReceiver receiver){
System.out.println(receiver.getInfo());
}
}
依赖的3种实现传递的方式
方式1:通过接口传递实现依赖
public class DependenceReversal {
public static void main(String[] args) {
ChangHong changHong = new ChangHong();
OpenAndClose openAndClose = new OpenAndClose();
openAndClose.open(changHong);
}
}
//方式1:通过接口传递实现依赖
// 开关接口
interface IOpenAndClose{
public void open(ITV itv);
}
//ITV接口
interface ITV{
public void play();
}
//长虹电视实现
class ChangHong implements ITV{
@Override
public void play() {
System.out.println("打开长虹电视机");
}
}
//实现接口
class OpenAndClose implements IOpenAndClose{
@Override
public void open(ITV itv) {
itv.play();
}
}
方式2:通过构造方法依赖传递
public class DependenceReversal {
public static void main(String[] args) {
ChangHong2 changHong2 = new ChangHong2();
OpenAndClose2 close2 = new OpenAndClose2(changHong2);
close2.open();
}
}
//方式2:通过构造方法依赖传递
interface IOpenAndClose2{
public void open();
}
interface ITV2{
public void play();
}
//长虹电视实现
class ChangHong2 implements ITV2{
@Override
public void play() {
System.out.println("打开长虹电视机");
}
}
class OpenAndClose2 implements IOpenAndClose2{
public ITV2 itv;
public OpenAndClose2(ITV2 itv){
this.itv = itv;
}
@Override
public void open() {
this.itv.play();
}
}
方式3:通过setter方法传递
public class DependenceReversal {
public static void main(String[] args) {
ChangHong3 changHong3 = new ChangHong3();
OpenAndClose3 close3 = new OpenAndClose3();
close3.setTv(changHong3);
close3.open();
}
}
//方式3:通过setter方法传递
interface IOpenAndClose3{
public void open();
public void setTv(ITV3 tv);
}
interface ITV3{
public void play();
}
//长虹电视实现
class ChangHong3 implements ITV3{
@Override
public void play() {
System.out.println("打开长虹电视机");
}
}
class OpenAndClose3 implements IOpenAndClose3{
public ITV3 itv;
@Override
public void open() {
this.itv.play();
}
@Override
public void setTv(ITV3 tv) {
this.itv = tv;
}
}
4. 里氏替换原则
继承必须确保超类所拥有的性质在子类中仍然成立。即继承父类而不去改变父类
代码示例
B类继承A类,增加一个新功能:两数相加,但是误将A类方法1重写,导致错误
//A类
class A{
//返回两数之差
public int func1(int num1,int num2){
return num1-num2;
}
}
//B类继承A类,增加一个新功能:两数相加,但是误将A类方法1重写,导致错误
class B extends A{
public int func1(int a,int b){
return a+b;
}
public int func2(int a,int b){
return func1(a,b)+9;
}
}
改进:创建一个更加基础的基类,把更加基础的方法和成员写在基类中,B类,如果需要使用A类方法,可采用组合方式
//创建一个更加基础的基类
class Base{
//把更加基础的方法和成员写在基类中
}
//A类
class A extends Base{
//返回两数之差
public int func1(int num1,int num2){
return num1-num2;
}
}
//B类,如果需要使用A类方法,可采用组合方式
class B extends Base{
private A al = new A();
public int func2(int a,int b){
return al.func1(a,b)+9;
}
}
5. 开闭原则
软件实体应当对扩展开放,对修改关闭。即扩展新类而不是修改旧类
(合成复用原则、里氏替换原则相辅相成,都是开闭原则的具体实现规范)
代码示例
以绘图为例,根据m_type不同调用不同方法绘制不同图形,优点是比较好理解,简单易操作。缺点是违反了设计模式的ocp原则,即对扩展开放,对修改关闭。即当我们给类增加新功能的时候,尽量不修改代码,或者尽可能少修改代码。比如我们这时要新增加一个图形种类,我们需要修改很多地方(创建新类,增加方法,增加判断)。
public class OpenAndClose {
public static void main(String[] args) {
GraphicEditor editor = new GraphicEditor();
editor.drawShape(new Circle());
editor.drawShape(new Rectangle());
}
}
class GraphicEditor{
public void drawShape(Shape s){
if(s.m_type == 1){
drawRectangle(s);
}else {
drawCircle(s);
}
}
public void drawRectangle(Shape r){
System.out.println("矩形");
}
public void drawCircle(Shape r){
System.out.println("圆形");
}
}
class Shape{
int m_type;
}
class Rectangle extends Shape{
Rectangle(){
super.m_type = 1;
}
}
class Circle extends Shape{
Circle(){
super.m_type = 2;
}
}
改进:把Shape类做成抽象类,并提供一个抽象的draw方法,让子类去实现,这样我们有新的图形种类时,只需要让新的图形类继承Shape,并实现draw方法即可,使用方的代码就不需要修改,满足了开闭原则。
public class OpenAndClose {
public static void main(String[] args) {
GraphicEditor editor = new GraphicEditor();
editor.drawShape(new Circle());
editor.drawShape(new Rectangle());
}
}
class GraphicEditor{
public void drawShape(Shape s){
s.draw();
}
}
abstract class Shape{
public abstract void draw();
}
class Rectangle extends Shape{
@Override
public void draw() {
System.out.println("绘制矩形");
}
}
class Circle extends Shape{
@Override
public void draw() {
System.out.println("绘制圆形");
}
}
6. 迪米特法则
最少知识原则,只与你的直接朋友交谈,不跟“陌生人”说话。即无需直接交互的两个类,如果需要交互,使用中间者
直接朋友: 每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖,关联,组合,聚合等。其中,我们称出现成员变量,方法参数,方法返回值中的类为直接的朋友,而出现在局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量的形式出现在类的内部
代码示例
以学院和学校为例,打印出所有员工。分析SchoolManager直接朋友是Employee,CollegeManager,但是CollegeEmployee不是直接朋友而是以局部变量的方式出现在类内部,违法了迪米特法则
public class Dimilit {
public static void main(String[] args) {
SchoolManager schoolManager = new SchoolManager();
schoolManager.printAllEmployee(new CollegeManager());
}
}
//学校总部员工类
class Employee{
private String id;
public void setId(String id){
this.id = id;
}
public String getId(){
return id;
}
}
//学院员工类
class CollegeEmployee{
private String id;
public void setId(String id){
this.id = id;
}
public String getId(){
return id;
}
}
//管理学院类
class CollegeManager{
//返回学院员工
public List<CollegeEmployee> getAllEmployee(){
List<CollegeEmployee> list = new ArrayList<CollegeEmployee>();
for (int i = 0; i < 10; i++) {
CollegeEmployee emp = new CollegeEmployee();
emp.setId("学院员工id= "+i);
list.add(emp);
}
return list;
}
}
//管理学校类
class SchoolManager{
//返回学校总部员工
public List<Employee> getAllEmployee(){
List<Employee> list = new ArrayList<Employee>();
for (int i = 0; i < 5; i++) {
Employee emp = new Employee();
emp.setId("学院员工id= "+i);
list.add(emp);
}
return list;
}
//打印所有员工
void printAllEmployee(CollegeManager collegeManager){
List<CollegeEmployee> list1 = collegeManager.getAllEmployee();
System.out.println("------------学院员工-------------");
for (CollegeEmployee collegeEmployee : list1) {
System.out.println(collegeEmployee.getId());
}
List<Employee> list2 = this.getAllEmployee();
System.out.println("------------总部员工-------------");
for (Employee employee : list2) {
System.out.println(employee.getId());
}
}
}
改进:将输出学院的员工方法,封装到CollegeManager
public class Dimilit {
public static void main(String[] args) {
SchoolManager schoolManager = new SchoolManager();
schoolManager.printAllEmployee(new CollegeManager());
}
}
//学校总部员工类
class Employee{
private String id;
public void setId(String id){
this.id = id;
}
public String getId(){
return id;
}
}
//学院员工类
class CollegeEmployee{
private String id;
public void setId(String id){
this.id = id;
}
public String getId(){
return id;
}
}
//管理学院类
class CollegeManager{
//返回学院员工
public List<CollegeEmployee> getAllEmployee(){
List<CollegeEmployee> list = new ArrayList<CollegeEmployee>();
for (int i = 0; i < 10; i++) {
CollegeEmployee emp = new CollegeEmployee();
emp.setId("学院员工id= "+i);
list.add(emp);
}
return list;
}
//输出学院员工信息
public void printEmployee(){
List<CollegeEmployee> list1 = getAllEmployee();
System.out.println("------------学院员工-------------");
for (CollegeEmployee collegeEmployee : list1) {
System.out.println(collegeEmployee.getId());
}
}
}
//管理学校类
class SchoolManager{
//返回学校总部员工
public List<Employee> getAllEmployee(){
List<Employee> list = new ArrayList<Employee>();
for (int i = 0; i < 5; i++) {
Employee emp = new Employee();
emp.setId("学院员工id= "+i);
list.add(emp);
}
return list;
}
//打印所有员工
void printAllEmployee(CollegeManager collegeManager){
//输出学院员工
collegeManager.printEmployee();
//输出学校员工
List<Employee> list2 = this.getAllEmployee();
System.out.println("------------总部员工-------------");
for (Employee employee : list2) {
System.out.println(employee.getId());
}
}
}
7. 合成复用原则
软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。即优先组合,其次继承
代码示例
Head类与Person类同生死,组合关系;IDCard类与Person,聚合关系
class Person {
private IDCard card; //聚合关系
private Head head = new Head(); //组合关系
}
class IDCard {
}
class Head {
}
四.设计模式核心思想
1. 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一 起
2. 针对接口编程,而不是针对实现编程
3. 为了交互对象之间的松耦合设计而努力