文章目录
设计模式
软件设计模式(Design pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。
设计模式的本质:
面向对象设计原则的实际运用,是对类的封装,继承,多态以及类的关联关系和组合关系的充分理解。
何时会用到设计模式
设计模式的重要性:
对软件设计中普遍存在(重复出现)的问题所提出的一系列的解决问题的方法。
设计模式在软件中的哪里?
面向对象=>功能模块[设计模式+算法[数据结构]]=>框架[多种设计模式]=>架构[服务群集群技术]
采用设计模式的目的
在编写代码的过程中,程序员会面临耦合性,内聚性,可维护性,可扩展性,灵活性,重用性等一系列多方面的要求,设计模式是为了使程序有更好的可读性,代码重用性,可维护和课扩展性,也可以使程序具有高内聚,低耦合的特性。
设计模式的七大原则
七大原则核心思想
1、单一职责原则
概念
一个类只负责一个职责,如果违背单一职责原则,建议使用分类或者添加方法来实现单一职责原则。
案例分析
违反单一职责原则案例
package com.tedu.principle.single;
//单一职责原则
public class SingleResponsitity1 {
public static void main(String[] args) {
Vehicle vehicle = new Vehicle();
vehicle.run("摩托车");
vehicle.run("汽车");
vehicle.run("飞机");
}
}
//例子
class Vehicle{
/**
* 在该方法中违反了单一职责原则,
* 解决方法:分类即可
* @param vehicle
*/
public void run(String vehicle){
System.out.println(vehicle + "在公路上跑");
}
}
解决方案1
package com.tedu.principle.single;
public class SingleResponsitity2 {
public static void main(String[] args) {
RoadVehicle vehicle = new RoadVehicle();
vehicle.run("摩托车");
vehicle.run("汽车");
AirVehicle vehicle1 = new AirVehicle();
vehicle1.run("飞机");
}
}
//例子
/**
* 遵守了单一职责原则,改动很大,成本太大
*/
class RoadVehicle{
public void run(String vehicle){
System.out.println(vehicle+"在公路上运行");
}
}
//例子
class AirVehicle{
public void run(String vehicle){
System.out.println(vehicle+"在天空上运行");
}
}
//例子
class WaterVehicle{
public void run(String vehicle){
System.out.println(vehicle+"在水上运行");
}
}
解决方案2
package com.tedu.principle.single;
public class SingleResponsitity3 {
public static void main(String[] args) {
Vehicle2 vehicle = new Vehicle2();
vehicle.run("摩托车");
vehicle.run("汽车");
vehicle.runAir("飞机");
}
}
//例子
class Vehicle2{
/**
* 这种修改方法没有对原来的类做大的修改,只是增加了方法,
* 在类上违反了单一职责原则(同类不存在),但是方法上实现了单一原则(不算违反单一职责原则)
* @param vehicle
*/
public void run(String vehicle){
System.out.println(vehicle + "在公路上跑");
}
public void runAir(String vehicle){
System.out.println(vehicle + "在天空上跑");
}
public void runWater(String vehicle){
System.out.println(vehicle + "在水上跑");
}
}
注意事项和细节
- 降低类的复杂度,一个类只负责一个职责
- 提高类的可读性和可维护性
- 降低变更引起的风险
- 我们应当遵守单一职责原则,只是逻辑足够简单,才可以在代码级违反单一职责原则,如果类中的方法足够简单,可以在方法上保持单一职责原则
2、接口隔离原则(Interface Segregation Princple)
概念
客户端不应该依赖他不需要的接口,一个类对另一个类的依赖,建立在最小的接口上.
**用法:**将接口拆分成几个独立的几个接口,不同的类分别与他们需要的接口建立依赖关系
案列分析
Java实现代码
package com.tedu.principle.segregation;
public class Segregation1 {
public static void main(String[] args) {
}
}
//接口
interface Segregation01{
void operation01();
void operation02();
void operation03();
void operation04();
void operation05();
}
class B implements Segregation01{
@Override
public void operation01() {
System.out.println("B实现了operation方法1");
}
@Override
public void operation02() {
System.out.println("B实现了operation方法2");
}
@Override
public void operation03() {
System.out.println("B实现了operation方法3");
}
@Override
public void operation04() {
System.out.println("B实现了operation方法4");
}
@Override
public void operation05() {
System.out.println("B实现了operation方法5");
}
}
class D implements Segregation01{
@Override
public void operation01() {
System.out.println("D实现了operation方法1");
}
@Override
public void operation02() {
System.out.println("D实现了operation方法2");
}
@Override
public void operation03() {
System.out.println("D实现了operation方法3");
}
@Override
public void operation04() {
System.out.println("D实现了operation方法4");
}
@Override
public void operation05() {
System.out.println("D实现了operation方法5");
}
}
class A { //A通过依赖的方式使用了Segregation01中的方法1,2,3
public void depend01(Segregation01 seg){
seg.operation01();
}
public void depend02(Segregation01 seg){
seg.operation02();
}
public void depend03(Segregation01 seg){
seg.operation03();
}
}
class C { //A通过依赖的方式使用了Segregation01中的方法1,4,5
public void depend01(Segregation01 seg){
seg.operation01();
}
public void depend04(Segregation01 seg){
seg.operation04();
}
public void depend05(Segregation01 seg){
seg.operation05();
}
}
以上代码存在代码重复和代码冗余的问题,修改代码如下:
package com.tedu.principle.segregation;
public class Segregation2 {
public static void main(String[] args) {
A1 a = new A1();
a.depend01(new B1());
a.depend02(new B1());
a.depend03(new B1());
C1 c = new C1();
c.depend01(new D1());
c.depend04(new D1());
c.depend05(new D1());
}
}
interface Segregation02{
void operation01();
}
/**
* interface1和interface2是
*/
interface Interface1 {
void operation02();
void operation03();
}
interface Interface2{
void operation04();
void operation05();
}
class B1 implements Segregation02,Interface1{
@Override
public void operation01() {
System.out.println("B1实现了Segregation01中的operation方法1");
}
@Override
public void operation02() {
System.out.println("B1实现了Interface1中的operation方法2");
}
@Override
public void operation03() {
System.out.println("B1实现了Interface1中的operation方法3");
}
}
class D1 implements Segregation02,Interface2{
@Override
public void operation01() {
System.out.println("D1实现了Segregation01中的operation方法1");
}
@Override
public void operation04() {
System.out.println("D1实现了Interface2中的operation方法4");
}
@Override
public void operation05() {
System.out.println("D1实现了Interface2中的operation方法5");
}
}
class A1 {
public void depend01(Segregation02 seg){
seg.operation01();
}
public void depend02(Interface1 interface1){
interface1.operation02();
}
public void depend03(Interface1 interface1){
interface1.operation03();
}
}
class C1 {
public void depend01(Segregation02 seg){
seg.operation01();
}
public void depend04(Interface2 interface2){
interface2.operation04();
}
public void depend05(Interface2 interface2){
interface2.operation05();
}
}
3、依赖倒转(倒置)原则
概念
- 高层模块不应该依赖底层模块,二者都应该依赖其抽象
- 抽象不应该依赖于细节,细节应该依赖其抽象
- 依赖倒置原则的中心思想是面向接口编程
- 相对于细节的多边性,抽象的事物要稳定的多,在Java中,抽象相当于是接口,细节相当于是接口的实现类
- 展现事物的细节交给实现类去完成
案列分析
没有使用依赖倒置原则
package com.tedu.principle.inversion;
//依赖倒置原则
public class DependenceInversion {
public static void main(String[] args) {
Person person = new Person();
person.receive(new Email());
}
}
class Email{
public String getInfo(){
return "电子邮件信息,Hello World!";
}
}
//完成person接收消息的功能
//方式1:不使用依赖倒置原则
/**
* 如果我们想增加获取对象:微信,短信,qq等,那么会增加类,增加person的方法(显然不可取)
*/
class Person{
public void receive(Email email){
System.out.println(email.getInfo());
}
}
使用了依赖倒置原则
package com.tedu.principle.inversion;
//依赖倒置原则
public class DependenceInversion02 {
public static void main(String[] args) {
Person1 person = new Person1();
person.receive(new Email1());
person.receive(new WeiXin());
}
}
//定义接口
interface IReceive{
public String getInfo();
}
class Email1 implements IReceive{
public String getInfo(){
return "电子邮件信息,Hello World!";
}
}
class WeiXin implements IReceive{
@Override
public String getInfo() {
return "微信接受的信息:Hello";
}
}
//完成person接收消息的功能
//方式1:不使用依赖倒置原则
/**
* 如果我们想增加获取对象:微信,短信,qq等,那么会增加类,增加person的方法(显然不可取)
* 解决思路:引入一个抽象的接口:IReceive,表示接收者,这样Person类和IReceive发生依赖
* 因为Email,微信等都属于接收者的范围,他们各自实现了IReceive就ok,这样就符合依赖倒置原则
*/
class Person1{
public void receive(IReceive iReceive){
System.out.println(iReceive.getInfo());
}
}
依赖倒置原则常用的三种方式及其代码示例
接口传递:
代码示例
interface OpenAndClose01{
public void open(Television1 television);
}
interface Television1{
public void play();
}
class OpenAndClose1 implements OpenAndClose01{
@Override
public void open(Television1 television) {
television.play();
}
}
构造方法传递:
代码示例
interface OpenAndClose02{
public void open();
}
interface Television2{
public void play();
}
class OpenAndClose2 implements OpenAndClose02{
public Television2 television2;
public OpenAndClose2(Television2 television2){
this.television2 = television2;
}
@Override
public void open() {
this.television2.play();
}
}
setter传递
代码示例
interface OpenAndClose03{
public void open();
public void setTelevision(Television3 television);
}
interface Television3{
public void play();
}
class OpenAndClose3 implements OpenAndClose03{
private Television3 television3;
@Override
public void open() {
this.television3.play();
}
@Override
public void setTelevision(Television3 television3) {
this.television3 = television3;
}
}
注意事项和细节
- 低层模块尽量要有抽象类或者是接口,或者两者都有,程序的稳定性更好;
- 变量的声明类型尽量是接口,这样方便以后的程序扩展和优化
- 继承时遵循里氏替换原则
4、里氏替换原则
继承存在的问题
- 父类中凡是已经实现好的方法,实际上是在设定规范和契约,虽然他不强制要求所有的子类必须遵守这些契约,但如果子类对这些已经实现的方法任意更改,就会对整个系统造成破坏,
- 继承再给程序设计带来便利的同时,也带了弊端,比如使用继承会给程序带来侵入性,程序的课移植性降低,增加对象的耦合性。则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子类的功能都有可能产生故障;
- 解决上述问题:里氏替换原则
概念
- 在继承过程中,在子类中尽量不要重写父类的方法
- 在适当的情况下可以通过聚合和组合和依赖解决这些问题
案列分析
在不小心情况下重写了代码
package com.tedu.principle.liskov;
public class Liskov {
public static void main(String[] args) {
A a = new A();
System.out.println("11-3=" + a.func1(11, 3));
System.out.println("1-8=" + a.func1(1, 8));
System.out.println("----------- ");
B b = new B();
System.out.println("11-3=" + b.func1(11, 3));
System.out.println("1-8=" + b.func1(1, 8));
System.out.println("11+3+9=" + b.func2(11, 3));
}
}
//A类,返回两个数的差
class A {
public int func1(int num1, int num2) {
return num1 - num2;
}
}
//增加功能,但是出现问题
/**
* 我们发现原来运行正常的相减功能发生了错误。原因就是类B无意中重写了父类的方法,
* 造成原有功能出现错误。在实际编程中,我们常常会通过重写父类的方法完成新的功能,
* 这样写起来虽然简单,但整个继承体系的复用性会比较差。特别是运行多态比较频繁的时候
*
* 解决办法:
* 通用的做法是:原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依赖,
* 聚合,组合等关系代替.
*/
class B extends A {
@Override
public int func1(int num1, int num2) {
return num1 + num2;
}
public int func2(int num1, int num2) {
return func1(num1, num2) + 9;
}
}
使用里氏替换原则的代码:
package com.tedu.principle.liskov;
import java.util.Base64;
public class ImproveLiskov {
public static void main(String[] args) {
A1 a = new A1();
System.out.println("11-3=" + a.func1(11, 3));
System.out.println("1-8=" + a.func1(1, 8));
System.out.println("----------- ");
B1 b = new B1();
System.out.println("11-3=" + b.func1(11, 3));
System.out.println("1-8=" + b.func1(1, 8));
System.out.println("11+3+9=" + b.func2(11, 3));
}
}
class Base{
//将更加基础的方法和属性写进基类中
}
//A类,返回两个数的差
class A1 extends Base {
public int func1(int num1, int num2) {
return num1 - num2;
}
}
//增加功能,
class B1 extends Base {
//如果B1要使用A1中的方法,使用组合关系
private A1 a = new A1();
public int func1(int num1, int num2) {
return num1 - num2;
}
public int func2(int num1, int num2) {
return func1(num1, num2) + 9;
}
public int func3(int num1,int num2){
return a.func1(num1,num2);
}
}
注意事项和细节
我们发现原来运行正常的相减功能发生了错误。原因就是类B无意中重写了父类的方法,造成原有功能出现错误。在实际编程中,我们常常会通过重写父类的方法完成新的功能,这样写起来虽然简单,但整个继承体系的复用性会比较差。特别是运行多态比较频繁的时候
5、开闭原则
概念
基本介绍
- 编程中最基础最重要的原则
- 用抽象构建框架,用实体扩展细节
- 通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码实现变化
- 使用设计模式的母的就是遵循开闭原则
案列分析
package com.tedu.principle.ocp;
//开闭原则
public class Ocp {
public static void main(String[] args) {
GraphicEditor graphicEditor = new GraphicEditor();
graphicEditor.drawShape(new Rectangle());
graphicEditor.drawShape(new Circle());
}
}
//这是一个用于绘图的类
class GraphicEditor{
//接受shape的对象,然后根据不同的type来绘制不同的图形
public void drawShape(Shape shape){
if (shape.m_type==1){
drawRectangle(shape);
}else if(shape.m_type==2){
drawCircle(shape);
}
}
private void drawRectangle(Shape shape) {
System.out.println("绘制矩形");
}
private void drawCircle(Shape shape) {
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;
}
}
上述代码存在问题:如果想要新增一个新的图形,就会违背ocp原则中的第三点
新增之后的代码
package com.tedu.principle.ocp;
//开闭原则
public class Ocp {
public static void main(String[] args) {
GraphicEditor graphicEditor = new GraphicEditor();
graphicEditor.drawShape(new Rectangle());
graphicEditor.drawShape(new Circle());
graphicEditor.drawShape(new Triangle());
}
}
//这是一个用于绘图的类
class GraphicEditor{
//接受shape的对象,然后根据不同的type来绘制不同的图形
public void drawShape(Shape shape){
if (shape.m_type==1){
drawRectangle(shape);
}else if(shape.m_type==2){
drawCircle(shape);
}else if(shape.m_type == 3){
drawTriangle(shape);
}
}
private void drawRectangle(Shape shape) {
System.out.println("绘制矩形");
}
private void drawCircle(Shape shape) {
System.out.println("绘制圆形");
}
//新增绘制三角形
private void drawTriangle(Shape shape){
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;
}
}
//新增三角形
class Triangle extends Shape{
Triangle(){
super.m_type = 3;
}
}
使用开闭原则的代码分析
package com.tedu.principle.ocp;
public class ImproveOcp {
public static void main(String[] args) {
GraphicEditor1 graphicEditor = new GraphicEditor1();
graphicEditor.drawShape(new Rectangle1());
graphicEditor.drawShape(new Circle1());
graphicEditor.drawShape(new Triangle1());
graphicEditor.drawShape(new OtherGraph());
}
}
/**
* 鉴于在Ocp中出现的问题的解决思路:
*
*/
//这是一个用于绘图的类
class GraphicEditor1{
//接受shape的对象,然后根据不同的type来绘制不同的图形
public void drawShape(Shape1 shape) {
shape.draw();
}
}
//基类
abstract class Shape1{
int m_type;
public abstract void draw();
}
//矩形类
class Rectangle1 extends Shape1{
Rectangle1(){
super.m_type = 1;
}
@Override
public void draw() {
System.out.println("绘制矩形");
}
}
//圆形类
class Circle1 extends Shape1{
Circle1(){
super.m_type = 2;
}
@Override
public void draw() {
System.out.println("绘制圆形");
}
}
//新增三角形
class Triangle1 extends Shape1{
Triangle1(){
super.m_type = 3;
}
@Override
public void draw() {
System.out.println("绘制三角形");
}
}
//再次新增
class OtherGraph extends Shape1{
OtherGraph(){
super.m_type = 4;
}
@Override
public void draw() {
System.out.println("绘制其他图形");
}
}
6、迪米特法则
概念
基本介绍
- 一个对象对其他对象保持很少的了解
- 迪米特法则又叫最少知道原则:即一个类在对自己依赖的类知道的越少越好。即对于被依赖的类,该类的信息都尽量的放在类的内部,而只提供类的public方法
- 确定朋友的条件(是否满足迪米特法则)
1)当前对象本身(this) 2)以参量形式传入到当前对象方法中的对象 3)当前对象的实例变量直接引用的对象 4)当前对象的实例变量如果是一个聚集,那么聚集中的元素也都是朋友 5)当前对象所创建的对象
案列分析
不遵守迪米特法则:
package com.tedu.principle.demeter;
import java.util.ArrayList;
import java.util.List;
//不遵守迪米特法则
public class Demeter {
public static void main(String[] args) {
SchoolManager manager = new SchoolManager();
manager.printAll(new CollegeManager());
}
}
//学校总部职工
class Employee{
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
//学院里的员工类
class CollegeEmployee{
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
// 管理学院员工的类
// CollegeEmployee 不满足迪米特法则条件
class CollegeManager{
public List<CollegeEmployee> getAllEmployee(){
List<CollegeEmployee> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
CollegeEmployee employee = new CollegeEmployee();
employee.setId("学院员工 = " + i);
list.add(employee);
}
return list;
}
}
//学校管理
class SchoolManager{
public List<Employee> getAllEmployee(){
List<Employee> list = new ArrayList<Employee>();
for (int i = 0; i < 5; i++) {
Employee employee = new Employee();
employee.setId("学院总部员工 = " + i);
list.add(employee);
}
return list;
}
//该方法完成输出学校总部和学院员工信息的方法
void printAll(CollegeManager manager){
List<CollegeEmployee> list1 = manager.getAllEmployee();
System.out.println("-----学院员工-----");
for (CollegeEmployee employee: list1 ) {
System.out.println(employee.getId());
}
List<Employee> list2 = this.getAllEmployee();
System.out.println("-----学院总部员工-----");
for (Employee employee: list2 ) {
System.out.println(employee.getId());
}
}
}
遵守迪米特法则:
package com.tedu.principle.demeter;
import java.util.ArrayList;
import java.util.List;
//不遵守迪米特法则
public class Demeter2 {
public static void main(String[] args) {
SchoolManager manager = new SchoolManager();
manager.printAll(new CollegeManager());
}
}
//学校总部职工
class Employee1{
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
//学院里的员工类
class CollegeEmployee1{
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
// 管理学院员工的类
class CollegeManager1{
public List<CollegeEmployee1> getAllEmployee(){
List<CollegeEmployee1> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
CollegeEmployee1 employee = new CollegeEmployee1();
employee.setId("学院员工 = " + i);
list.add(employee);
}
return list;
}
public void printAll(){
List<CollegeEmployee1> list1 = this.getAllEmployee();
System.out.println("-----学院员工-----");
for (CollegeEmployee1 employee1: list1 ) {
System.out.println(employee1.getId());
}
}
}
class SchoolManager1{
public List<Employee1> getAllEmployee(){
List<Employee1> list = new ArrayList<Employee1>();
for (int i = 0; i < 5; i++) {
Employee1 employee = new Employee1();
employee.setId("学院总部员工 = " + i);
list.add(employee);
}
return list;
}
//该方法完成输出学校总部和学院员工信息的方法
void printAll(CollegeManager1 manager){
/*List<CollegeEmployee> list1 = manager.getAllEmployee();
System.out.println("-----学院员工-----");
for (CollegeEmployee employee: list1 ) {
System.out.println(employee.getId());
}*/
manager.printAll();
List<Employee1> list2 = this.getAllEmployee();
System.out.println("-----学院总部员工-----");
for (Employee1 employee: list2 ) {
System.out.println(employee.getId());
}
}
}
注意事项和细节
降低类之间的耦合关系,并不是完全没有依赖
7、合成复用原则
概念
尽量使用合成聚合的方式来书写代码,而不是使用继承。
总结:
- 将应用中可能需要变化之处的代码独立出来,别和不需要变化的代码混在一起
- 针对接口编程,而不是针对实现编程
- 为了交互对象之间的松耦合设计