文章目录在面向对象设计中,可维护性的复用是以设计原则为基础的,每一个原则都蕴含一些面向对象设计的思想,可以从不同的角度提升软件结构的设计水平。
- 面向对象设计原则
- 1.1单一职责原则(SRP原则)
- 1.1.1代码实现
- 1.1.2单一职责原则注意事项和细节
- 1.2开闭原则(OCP原则)
- 1.2.1代码实现
- 1.3里氏代换原则(LSP原则)
- 1.3.1代码实现
- 1.4依赖倒转原则(DIP原则)
- 1.4.1代码实现:
- 1.4.2三种依赖关系传递
- 1.4.3依赖倒转原则的注意事项和细节
- 1.5接口隔离原则(ISP原则)
- 1.5.1代码实现:
- 1.6合成复用原则(CRP原则)
- 1.6.1用一个类图来表达就可以表达出来
- 1.6.2核心思想
- 1.7迪米特法则(LoD原则:最少知道原则)
- 1.7.1理解迪米特法则
- 1.7.2代码实现
- 1.7.3迪米特法则注意的细节:
面向对象设计原则
最常见的七个面向对象设计原则:
单一职责原则、开闭原则、里氏代换原则、依赖倒转原则、接口隔离原则、合成复用原则、迪米特法则。
定义:一个对象应该只包含单一的职责,并且该职责被完整的封装在一个类中。
在软件系统中,一个类(大到模块,小到方法)承担的职责越多,它被复用的可能性就越大,而且一个类承担的职责过多,相当于将这些职责耦合在一起,当其中一个职责发生变化时可能会影响其他职责运作,因此要将这些职责进行分离,将不同的职责封装在不同的类中,即 将不同的变化原因封装在不同的类中,如果多个职责总是同时发生改变则将它们封装在同一类中。
单一职责原则是实现高内聚、低耦合的指导方针。
例如:对于客户关系管理系统中的客户信息图形统计模块提出的初始设计方案。
初始设计方案结构图
重构后的结构图
重构前CustormerDataChart类方法中承担了太多的职责;重构后DBUtil负责链接数据库,包含数据库连接方法;Customer
DAO负责 *** 作数据库中的Customer表,包含对Customer表的增删改查等方法;CustomerDataChart负责图表的生成和显示。
(通过这个类图可以简单理解一下单一职责原则原理,就是能拆就拆)
(这个代码实现并不是上面类图的代码实现,通过更简单的例子理解单一职责原则)
package com.at;
public class SingleResponsibilityPrinciple {
public static void main(String[] args) {
Vehicle vehicle = new Vehicle();
vehicle.run("摩托车");
vehicle.run("汽车");
vehicle.run("飞机");
}
}
//交通工具类
//方式1
//1、在方式1中的run方法中,违反了单一职责原则
//2,解决:根据交通工具运行方法不同,分解成不同类即可。
class Vehicle{
public void run(String vehicle){
System.out.println(vehicle+"在公路上运行。。。");
}
}
遵守了单一职责原则后:
package com.at;
public class SingleResponsibilityPrinciple2 {
public static void main(String[] args) {
RoadVehicle roadVehicle = new RoadVehicle();
roadVehicle.run("摩托车");
roadVehicle.run("汽车");
AirVehicle airVehicle = new AirVehicle();
airVehicle .run("飞机");
}
}
//1、遵守单一职责原则
//2、改动大,类分解,同时修改客户端
//3、改进:直接对vechicle改动
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+"水中运行");
}
}
但是在逻辑足够简单的情况下还可以及只在vehile类上边做改动:
package com.at;
public class SingleResponsibilityPrinciple3 {
public static void main(String[] args) {
Vehicle2 vehicle2 = new Vehicle2();
vehicle2.run("摩托车");
vehicle2.runWater("轮船");
vehicle2.runAir("飞机");
}
}
//1、这种修改没有对原来的类作大的改动,只是增加了方法
//2、虽然没有在类这个级别上遵守单一职责原则,但是在方法级别上,仍然遵守单一职责原则
class Vehicle2{
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+"在公路上运行。。。");
}
}
1.1.2单一职责原则注意事项和细节
1、降低类的复杂度,达到一个类只负责一项职责
2、提高类的可读性,可维护性
3、降低变更代码的风险
4、通常情况下,应当严格遵守单一职责原则,只有逻辑足够简单,才可以在代码级违反(前提条件逻辑简单)单一职责原则;只有类中方法数量足够少,可以在方法级别保持单一职责原则。
定义:软件实体应当对扩展开放,对修改关闭。
1、开闭原则是编程中最基础、最重要的设计原则,其实,基本上大部分原则都是为了达到开闭原则。
2、一个软件实体比如类,模块和函数应该对扩展开放(对提供方),对修改关闭(对使用方)。用抽象构建框架,用实现扩展细节。
3、在开闭原则的定义中,软件实体可以指一个软件模块、一个由多个类组成的局部结构或一个独立的类。开闭原则就是指软件实体应尽量在不修改原有代码的情况下进行扩展。
4、就是任何软件在随着时间的推移中,总有需求更新,在不更改源代码的情况下进行扩展,具有非常好的灵活性和适应性,同时具备良较好的稳定性和延续性。
5、编程中遵循其他的原则,以及使用设计模式的目的就是遵循开闭原则。
例如:设计一个画图形的功能,看下面类图设计
package OPenClosed;
public class OpenClosedPrinciple {
public static void main(String[] arge) {
//使用
GraphicEditor graphicEditor = new GraphicEditor();
graphicEditor.drawShape(new Rectangle());
graphicEditor.drawShape(new Circle());
}
}
//绘图类
class GraphicEditor{
//接收Shape对象,然后更具type,来绘制不同的图形
public void drawShape(Shape s) {
if (s.m_type == 1)drawRectangle(s);
else if(s.m_type == 2)drawRectangle(s);
}
public void drawRectangle(Shape r) {
System.out.println("矩形");//画矩形
}
public void drawCircle(Shape r) {
System.out.println("圆形");
}
}
class Shape{//Shape基类
int m_type;
}
class Rectangle extends Shape{
Rectangle(){
super.m_type=1;
}
}
class Circle extends Shape{
Circle(){
super.m_type = 2;
}
}
/*
* 分析,优点就是比较好理解,简单易 *** 作。
* 缺点呢违反了设计模式的OCP原则,即对外开放(提供方),对修改关闭(使用方)。即当我们给类增加新功能的时候,尽量不要修改代码,或者尽可能的少修改代码
* 例如这个时候给他增加一个新的图形种类,我们需要修改的地方比较多
*/
例如我们新增一个三角形
package OPenClosed;
public class OpenClosedPrinciple {
public static void main(String[] arge) {
//使用
GraphicEditor graphicEditor = new GraphicEditor();
graphicEditor.drawShape(new Rectangle());
graphicEditor.drawShape(new Circle());
graphicEditor.drawShape(new Triangle());
}
}
//绘图类[使用方]
class GraphicEditor{
//接收Shape对象,然后更具type,来绘制不同的图形
public void drawShape(Shape s) {
if (s.m_type == 1)drawRectangle(s);
else if(s.m_type == 2)drawCircle(s);
** else if(s.m_type == 2)drawTriangle(s);//新增的三角形
}
public void drawRectangle(Shape r) {
System.out.println("矩形");//画矩形
}
public void drawCircle(Shape r) {
System.out.println("圆形");
}
** public void drawTriangle(Shape r) {
System.out.println("三角形");//增加了新的方法
}
}
class Shape{//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;
}
}
可以看出,还是能够运行,但是呢违背了OCP原则,修改处我用*号标出来了,可以看出如果新增一个功能修改的地方还是有一些多。
接下来用遵守OCP原则修改方法:
思路很简单,把创建Shape类做成抽象类,并提供一个抽象的draw方法,让子类去实现即可,这样我们就有新的图形种类时,只需要让新的图形类继承Shape,并且实现draw方法即可,使用方的代码就不用修改,满足了OCP原则。
改进后的代码
如果我们使用OCP原则去编写代码:
package OPenClosed;
public class OpenClosedPrinciple {
public static void main(String[] arge) {
//使用
GraphicEditor graphicEditor = new GraphicEditor();
graphicEditor.drawShape(new Rectangle());
graphicEditor.drawShape(new Circle());
graphicEditor.drawShape(new Triangle());
}
}
//绘图类
class GraphicEditor{
//接收Shape对象,然后更具type,来绘制不同的图形
public void drawShape(Shape s) {
s.draw();
}
}
abstract class Shape{//Shape基类
int m_type;
public abstract void draw();//抽象方法
}
class Rectangle extends Shape{
Rectangle(){
super.m_type=1;
}
@Override
public void draw() {
// TODO Auto-generated method stub
System.out.println("绘制矩形");
}
}
class Circle extends Shape{
Circle(){
super.m_type = 2;
}
@Override
public void draw() {
// TODO Auto-generated method stub
System.out.println("绘制圆形");
}
}
class Triangle extends Shape{
Triangle(){
super.m_type = 3;
}
@Override
public void draw() {
// TODO Auto-generated method stub
System.out.println("绘制三角形");
}
}
再新增就很简单了,例如新增一个其他图形
package OPenClosed;
public class OpenClosedPrinciple {
public static void main(String[] arge) {
//使用
GraphicEditor graphicEditor = new GraphicEditor();
graphicEditor.drawShape(new Rectangle());
graphicEditor.drawShape(new Circle());
graphicEditor.drawShape(new Triangle());
** graphicEditor.drawShape(new OtherGraphic());
}
}
//绘图类
class GraphicEditor{
//接收Shape对象,调用draw方法
public void drawShape(Shape s) {
s.draw();
}
}
abstract class Shape{//Shape基类
int m_type;
public abstract void draw();//抽象方法
}
class Rectangle extends Shape{
Rectangle(){
super.m_type=1;
}
@Override
public void draw() {
// TODO Auto-generated method stub
System.out.println("绘制矩形");
}
}
class Circle extends Shape{
Circle(){
super.m_type = 2;
}
@Override
public void draw() {
// TODO Auto-generated method stub
System.out.println("绘制圆形");
}
}
class Triangle extends Shape{
Triangle(){
super.m_type = 3;
}
@Override
public void draw() {
// TODO Auto-generated method stub
System.out.println("绘制三角形");
}
}
**class OtherGraphic extends Shape{
OtherGraphic(){
super.m_type = 4;
}
@Override
public void draw() {
// TODO Auto-generated method stub
System.out.println("绘制其他形");
}
}
//依赖倒转、单一原则也是类似的,使用方都不用改变
这样就很简单了。
定义:所有引用基类的地方必须能透明地使用其子类的对象。
1、在运用里氏代换原则时应该将父类设计为抽象类或者接口,让子类继承父类或实现父接口,并实现在父类声明的方法,在运行时子类实例替换父类实例,可以很方便地扩展系统的功能,无须修改原有子类的代码,增加新的功能可以通过增加一个新的子类来实现。
2、继承包含这样一层含义:父类中凡是已经实现好的方法,实际上是在设定规范和契约,虽然它不强制要求所有的子类必须遵循这些契约,但是如果子类对这些已经实现的方法任意修改,就会对整个继承体系造成破坏。
3、继承在给程序设计带来便利的同时,也会带来弊端。比如使用继承会给程序带来侵入性,程序的可移植性降低,增加对象间的耦合性,如果一个类被其他的类所继承,则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到的子类都有可能产生故障。
4、在使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法。
5、继承实际上让两个类耦合性增强了,在适当的情况下,可以通过聚合,组合,依赖来解决问题。
6、里氏替换原则严格表述:如果每一个类型为S的对象o1都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都代换o2时程序P的行为没有变化,那么类型S是类型T的子类型。
例如:问题1
package Liskov;
public class liskov {
public static void main(String[] args) {
A a = new A();//创建一个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();//创建B的实例
System.out.println("11-3="+b.func1(11, 3));//本意是求出11-3的结果
System.out.println("1-8="+b.func1(1, 8));//求出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类继承A类
//增加了一个新的功能:完成两个数的相加,然后和9相加,
class B extends A{
//重写了A类的方法,会出现方法重写
public int func1(int a,int b) {
return a + b;
}
public int func2(int a,int b) {
return func1(a,b)+9;
}
}
}
可以发现正常的相减功能发生了错误。其原因就是B类无意中重写了父类的方法,造成原有的功能出现错误。在实际编程中,我们常常会通过重写父类的方法完成新的功能,这样写起来虽然简单,但是整个继承体系的复用性会比较差。特别是运行多态比较频繁的时候。
通用的方法是:原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依赖,聚合,等关系来替代。
package Liskov.liskovPrinciple;
public class liskov {
public static void main(String[] args) {
A a = new A();//创建一个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();//创建B的实例
//因为B类不再继承A类,因此调用者,不会在func1是求减法
//调用完成的功能就会很明确
System.out.println("11-3="+b.func1(11, 3));//本意是求出11-3的结果
System.out.println("1-8="+b.func1(1, 8));//求出1-8的结果
System.out.println("11+3+9="+b.func2(11, 3));
//使用组合仍然可以使用到A类相关方法
System.out.println("11-3 = "+b.func3(11, 3));
}
//创建一个更加基础的基类
class Base{
//把更加基础的方法和成员写到Base类
}
//A类
class A extends Base{
//返回两个数的差
public int func1(int num1,int num2) {
return num1 - num2;
}
}
//B类继承A类
//增加了一个新的功能:完成两个数的相加,然后和9相加,
class B extends Base{
private A a = new A();
//重写了A类的方法,会出现方法重写
public int func1(int a,int b) {
return a + b;
}
public int func2(int a,int b) {
return func1(a,b)+9;
}
//仍然想要使用A的方法
public int func3(int a,int b ) {
return this.a.func1(a,b);
}
}
}
使用了组合实现减少方法重写,这样原来的问题就解决了。
定义:高层模块不应该依赖底层模块,它们都应该依赖抽象(抽象类、接口)。抽象不应该依赖细节,细节应该依赖于抽象。
1、简单来说,依赖倒转原则要求针对接口编程,不要针对实现编程。
2、依赖倒转原则的中心思想是面向接口编程。相对于细节的多变性,抽象的东西要稳定的多;以抽象为基础的框架要稳定的多;在Java中,抽象指的是接口或抽象类,细节就是具体的实现类;这是依赖倒转原则的设计理念。
3、使用接口或抽象类的目的是制定好规范,而不涉及任何具体的 *** 作,把展现细节的任务交给他们的实现类去完成。
package Dependence;
import Dependence.Person;
public class Dip{
//完成Person接收消息的功能
public static void main(String[] args){
Person person = new Person();
person.receive(new Email());//调用
}
}
//传统方式:比较简单,容易想到
//电子邮件类
class Email{
public String getInfo(){
return "电子邮件信息:hello world";//返回一个字符串
}
}
//如果我们获取的对象是微信,短信等等,则新增加类,同时Person也要增加相应的接收方法
//解决:引入一个抽象的接口Ireceiver,表示接收者,这样Person类就与接口Ireceiver发生依赖
//因为Email,微信等属于接收范畴,他们各自实现Ireceiver接口,这样我们就符合依赖倒转原则
//实现依赖倒转原则,只需要去依赖接口就行了
class Person{
public void receive(Email email){
System.out.println((email.getInfo()));//调用方法
}
}
package Dependences;
public class Dip2 {
//客户端无需改变
public static void main(String[] args){
Person person = new Person();
person.receiver(new Emails());
person.receiver(new Weixin());
}
}
//定义一个接口
interface IReceiver{
public String getInfo();//抽象方法
}
class Emails implements IReceiver{
public String getInfo() {
return"电子邮件信息";
}
}
//增加一个微信信息、只需要增加一个类和new一个微信
class Weixin implements IReceiver{
public String getInfo() {
return"微信";
}
}
class Person{
//依赖的是接口
public void receiver(IReceiver receiver) {
System.out.println(receiver.getInfo());
}
}
1.4.2三种依赖关系传递
依赖倒转原则有三种依赖关系传递
1、接口传递
public class InterfaceTransfer {
public static void main(String[] args){
ChangHong changHong = new ChangHong();
OpenAndClose openAndClose = new OpenAndClose();
openAndClose.open(changHong);
}
}
//方式一通过接口实现依赖
//接口开关
interface IOpenAndClose{
public void open(ITV tv) ;//抽象方法,接收接口
}
interface ITV{//ITV接口
public void play();//play 接口
}
class ChangHong implements ITV{
@Override
public void play() {
// TODO Auto-generated method stub
System.out.println("长虹电视,打开");
}
}
//实现接口
class OpenAndClose implements IOpenAndClose{
public void open (ITV tv) {
tv.play();//调用play接口
}
2、构造方法传递
public class InterfaceTransfer2 {
public static void main(String[] args){
ChamgHong changHong=new ChangHong();
//通过构造器进行依赖传递
OpenAndClose openAndClose = new OpenAndClose(changHong);
openAndClose.open();
}
}
interface IOpenAndClose{
public void open();//抽象方法
}
interface ITV {//ITV接口
public void play();
}
class OpenAndClose implements IOpenAndClose{
public ITV tv;//成员
public OpenAndClose(ITV tv) {//构造器
this.tv = tv;
}
public void open() {
this.tv.play();
}
class ChangHong implements ITV{
@Override
public void play() {
// TODO Auto-generated method stub
System.out.println("长虹电视,打开");
}
}
3、setter方式传递
public class setter {
public static void main(String[] args){
ChangHong changHong = new ChangHong();
//通过stter方法进行依赖传递
OpenAndClose openAndClose = new OpenAndClose();
openAndClose.setTv(changHong);
openAndClose.open();
}
}
interface IOpenAndClose{
public void open();//抽象方法
public void setTv(ITV tv);
}
interface ITV {//ITV接口
public void play();
}
class OpenAndClose implements IOpenAndClose{
private ITV tv;
public void setTv(ITV tv) {
this.tv = tv;
}
public void open() {
this.tv.play();
}
}
class ChangHong implements ITV{
@Override
public void play() {
// TODO Auto-generated method stub
System.out.println("长虹电视,打开");
}
1.4.3依赖倒转原则的注意事项和细节
1、低层模块尽量都要有抽象类或接口,或者两者都要有,程序稳定性更好。
2、变量的声明类型或接口,这样我们的变量引用和实际对象间,就存在一个缓冲层,利于程序扩展和优化。
3、继承时遵循里氏替换原则。
4、在大多数情况下,开闭原则、里氏代换原则、依赖倒转原则会同时出现,开闭原则是目标,里氏代换原则是基础,依赖倒转原则是手段,相辅相成,相互补充,目标一致,有时候只是分析问题所站的角度不同而已。
定义:客户端不应该依赖那些它不需要的接口;即一个类对另一个类的依赖应该建立在最小的接口上。
1、根据接口隔离原则,当一个接口太大时需要将它分割成一些更细小的接口,使用该接口的客户端仅需知道与之相关的方法即可。
2、当把“接口”理解成一个类型所提供的所有方法特征的集合的时候,这就是一种逻辑上的概念,接口的划分将直接带来类型的划分。可以把接口理解成角色,一个接口只能代表一个角色,每个角色都有它特定的一个接口,此时这个原则可以叫“角色隔离原则”。
3、如果把“接口”理解成狭义的特定语言的接口,那么ISP原则表达的意思就是指接口仅仅提供客户端需要的的行为,客户端不需要的行为则隐藏起来,应当为客户端提供尽可能小的单独接口,而不要提供大的总接口。
例如:类A通过通过接口interface1依赖类B,类C通过接口interface1依赖类D,如果接口interface1对于类A和类C来说不是最小接口,那么类B和类D必须去实现他们不需要的方法。
package com.company;
public class InterfaceSegregationPrinciple {
}
//创建Interface1(接口)
interface Interface1{
void operation1();
void operation2();
void operation3();
void operation4();
void operation5();
}
class B implements Interface1{
@Override
public void operation1() {
System.out.println("B实现了operation1");
}
public void operation2() {
System.out.println("B实现了operation2");
}
public void operation3() {
System.out.println("B实现了operation3");
}
public void operation4() {
System.out.println("B实现了operation4");
}
public void operation5() {
System.out.println("B实现了operation5");
}
}
class D implements Interface1{
@Override
public void operation1() {
System.out.println("D实现了operation1");
}
public void operation2() {
System.out.println("D实现了operation2");
}
public void operation3() {
System.out.println("D实现了operation3");
}
public void operation4() {
System.out.println("D实现了operation4");
}
public void operation5() {
System.out.println("D实现了operation5");
}
}
//类 A通过Interface1去依赖(使用)B
//但是A中只会使用到接口的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
//但是C中只会用到接口的1,4,5三个方法
class C{
public void depend1(Interface1 i){
i.operation1();
}
public void depend2(Interface1 i) {
i.operation4();
}
public void depend3(Interface1 i) {
i.operation5();
}
}
按照接口隔离原则应该这样处理:
将接口interface1拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则。
拆解后的UML图
package com.company;
public class InterfaceSegregationPrinciple {
}
//创建Interface1(接口)
interface Interface1{
void operation1();
}
//创建Interfac2接口
interface Interface2{
void operation2();
void operation3();
}
//创建Interface3接口
interface Interface3{
void operation4();
void operation5();
}
//创建B类
class B implements Interface1,Interface2{
@Override
public void operation1() {
System.out.println("B实现了operation1");
}
public void operation2() {
System.out.println("B实现了operation2");
}
public void operation3() {
System.out.println("B实现了operation3");
}
}
//创建D类
class D implements Interface1,Interface3{
@Override
public void operation1() {
System.out.println("D实现了operation1");
}
public void operation4() {
System.out.println("D实现了operation4");
}
public void operation5() {
System.out.println("D实现了operation5");
}
}
//类 A通过Interface1,Interface2接口去依赖(使用)B
class A{
public void depend1(Interface1 i){
i.operation1();
}
public void depend2(Interface2 i) {
i.operation2();
}
public void depend3(Interface2 i) {
i.operation3();
}
}
//类C通过Interface1,Interface3去依赖(使用)D
class C {
public void depend1(Interface1 i) {
i.operation1();
}
public void depend4(Interface3 i) {
i.operation4();
}
public void depend5(Interface3 i) {
i.operation5();
}
}
最后,在使用接口隔离原则时需要注意控制接口的粒度,接口不能太小,如果太小会导致系统中的接口泛滥,不利于维护;接口也不能太大,太大的接口将违背接口隔离原则,灵活性较差,使用起来很不方便。一般而言,在接口中仅包含为某一类用户定制的方法即可,不应该强迫客户依赖于那些他们不用的方法。
1.6合成复用原则(CRP原则)定义:优先使用对象组合,而不是通过继承来达到复用的目的。
原则就是尽量使用合成、聚合的方式,而不是使用继承
如果一开始考虑到继承方法,它们之间的关系不是那么密切的话,使用继承就会导致耦合性增强,如果B类用不了那么多方法,也会继承方法,如果还有其他的类继承了A类,其他类与A类关系比较密切,当其他类需要改变A类方法时,B类也会受到影响。所以,一下方法可以改变这种情况。
使用依赖的方式:传递A类的对象实例到B类。
通过聚合方法:增加一个属性,a的对象实例,增加一个setA方法,这就把A类聚合到B类中。
通过组合方式:增加一个属性a,类型A,当B对象实例创建好时,属性也就创建好了,相当于把A类组合到B类中。
1.6.2核心思想1、找出应用中可能需要变化之处,把他们独立出来,不要和那些不需要变化的代码混在一起。
2、针对接口编程,而不是针对实现编程。
3、为了交互对象之间的松耦合设计而努力。
定义:每一个软件单位对其他单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。
1.7.1理解迪米特法则1、一个对象应该对其他对象保持最少的了解。
2、类与类关系越密切,耦合度越大。
3、迪米特法则又叫最少知道原则,即一个类对自己依赖的类知道的越少越好。也就是说,对于被以来的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供一个public 方法,不对外泄露任何信息。
4、迪米特法则的定义可以简化为:只与直接的朋友通信。
5、直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式有很多,依赖、关联、组合、聚合等。其中,我们称出现成员变量,方法参数、方法返回值中的类为直接的朋友,而出现在局部变量中的类不是直接的朋友,也就是说,陌生的类最好不要以局部变量的形式出现在类的内部。
6、在迪米特法则中,对于一个对象,其朋友包括以下几类:
(1)、当前对象本身(this)。
(2)、以参数形式传入到当前对象方法中的对象。
(3)、当前对象的成员对象。
(4)、如果当前对象的成员对象是一个集合,那么集合中的元素也都是朋友。
(5)、当前对象所创建的对象。
任何一个对象如果满足上面的条件之一,就是当前对象的朋友,否则就是陌生人。
7、迪米特法则要求在设计系统时应该尽量减少对象之间的交互,就是通过引入一个合理的“第三者”来降低现有对象之间的耦合度。
应用实例:
有一个学校,下属各个学院和总部,现要求打印出学校总部员工ID和学院员工ID,编程实现功能。
package Demeter;
import java.util.ArrayList;
import java.util.List;
//客户端
public class demeter {
public static void main(String[] args) {
//创建了SchoolManager对象
SchoolManager schoolManager = new SchoolManager();
//输出学院的员工id和学校总部的员工id
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;
}
}
//管理学院员工的管理类
class CollegeManager{
//返回学院的所有员工
public List getAllEmoloyee(){
List List = new ArrayList();
for (int i = 0;i<10;i++) {//增加了10个员工到list集合里
CollegeEmployee emp = new CollegeEmployee();
emp.setId("学院员工ID = "+i);
List.add(emp);
}
return List;
}
}
//学校管理类
//分析SchoolManager 类的直接朋友有Employee、CollegeManager;
//CollegeEmployee不是它的直接朋友,而是一个陌生类,违背了迪米特法则
class SchoolManager{
//返回学校总部的员工
public ListgetAllEmployee(){
Listlist = new ArrayList();
for (int i = 0;i<5;i++) {//这里增加了5个员工到list
Employee emp = new Employee();
emp.setId("学校总部员工ID="+i);
list.add(emp);
}
return list;
}
//该方法完成输出学校总部和学院员工信息的方法(id)
void printAllEmployee(CollegeManager sub) {
//这里的CollegeEmployee不是SchoolManager的直接朋友
//CollegeEmployee 是以局部变量方式出现在SchoolManager中
//违反了迪米特法则
//获取到学院员工
Listlist1 = sub.getAllEmoloyee();
System.out.println("————学院员工————");
for(CollegeEmployee e :list1) {
System.out.println(e.getId());
}
//获取到学校总部员工
Listlist2 = this.getAllEmployee();
System.out.println("————学校总部员工————");
for(Employee e:list2) {
System.out.println(e.getId());
}
}
}
这是没有使用迪米特法则。修改很简单,只需要对CollegeEmployee 直接封装在CollegeManager这个类里面就行了。
package Demeter;
import java.util.ArrayList;
import java.util.List;
//客户端
public class demeter {
public static void main(String[] args) {
//创建了SchoolManager对象
SchoolManager schoolManager = new SchoolManager();
//输出学院的员工id和学校总部的员工id
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;
}
}
//管理学院员工的管理类
class CollegeManager{
//返回学院的所有员工
public List getAllEmoloyee(){
List List = new ArrayList();
for (int i = 0;i<10;i++) {//增加了10个员工到list集合里
CollegeEmployee emp = new CollegeEmployee();
emp.setId("学院员工ID = "+i);
List.add(emp);
}
return List;
}
//输出学院员工的信息
public void printEmployee() {
Listlist1 = this.getAllEmoloyee();
System.out.println("————学院员工————");
for(CollegeEmployee e :list1) {
System.out.println(e.getId());
}
}
//获取
}
//学校管理类
//分析SchoolManager 类的直接朋友有Employee、CollegeManager;
class SchoolManager{
//返回学校总部的员工
public ListgetAllEmployee(){
Listlist = new ArrayList();
for (int i = 0;i<5;i++) {//这里增加了5个员工到list
Employee emp = new Employee();
emp.setId("学校总部员工ID="+i);
list.add(emp);
}
return list;
}
//该方法完成输出学院员工信息的方法(id)
void printAllEmployee(CollegeManager sub) {
sub.printEmployee();
//获取到学院员工
Listlist2 = this.getAllEmployee();
System.out.println("————学校总部员工————");
for(Employee e:list2) {
System.out.println(e.getId());
}
}
}
1.7.3迪米特法则注意的细节:
1、将迪米特法则运用到系统设计中要注意以下几点:
(1)、在类的划分上应当尽量创建松耦合的类,类之间的耦合度越低,就越有利于复用。
(2)、在类的结构设计上,每一个类都应当尽量降低其成员变量和成员函数的访问权限。
(3)、在类的设计上,只要有可能,一个类型应当设计成不变类
(4)、在对其它类的引用上,一个对象对其他对象的引用应当将到最低。
2、迪米特法则的核心是降低类之间的耦合。
3、注意的是,由于每个类都减少了不必要的依赖,因此迪米特法则只只是要求降低类间(对象之间)耦合关系,并不是要求完全没有依赖。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)