UML中的UML类图,面向对象原则和方法复用-java篇

UML类图属于结构图,常被用于描述一个系统的静态结构。一个UML类图中通常包含类的UML图,接口的UML图,泛化关系的UML图,关联关系的UML图,依赖关系的UML图和实现关系的UML图。

类的UML图

在类的UML图中,使用一个长方形描述一个类的主要构成,将长方形垂直地分为三层。
       

第1层是名字层,如果类的名字是常规字形,则表明该类是具体类;如果类的名字是斜体字形,则表明该类是抽象类
第2层是变量层,也称属性层,列出类的成员变量类型,格式是“变量名字:类型”,在用UML表示类时,可以根据设计的需要只列出最重要的成员变量的名字。

如果变量的访问权限是 public,则需要在变量的名字前面用“+”符号修饰

如果变量的访问权限是 protected,则需要在变量的名字前面用“#"符号修饰

如果变量的访问权限是private,则需要在变量的名字前面用“-”符号修饰

如果变量的访问权限是友好的,则变量的名字前面不使用任何符号修饰。


第3层是方法层,也称操作层,列出类的方法返回类型,格式是“方法名字(参数列表):类型”

在用UML类时,可以根据设计需要只列出最重要的方法。

方法的访问权限是public,则需要在方法的名字前用的“+”号修饰;

方法的访问权限是 protected ,用“#”符号修饰;

方法的访问权限是private,用“-”符号修饰;

方法的访问权限是友好的,则方法的名字前面不使用任何符号修饰。

方法是静态方法,则在方法的名字下面添加下划线。

表示接口的UML图

使用一个长方形描述一个接口的主要构成,分为3层。
第1层是名字层,接口的名字必须是斜体字形用<<interface>>修饰名字
第2层是常量层,列出接口中的常量及类型,格式是“常量名字:类型”。

接口中常量的访问权限都是public,常量名字前用“+" 符号修饰
第3层是方法层,也称操作层,列出接口中的方法返回类型,格式是“方法名字(参数列表):类型”。接口中方法的访问权限都是public,在方法名字前面用“+"符号修饰

泛化关系

泛化关系(Generalization) 指类的继承关系。如果一个类是另一个类的子类,那么 UML通过使用一个实线连接两个类的UML图来表示二者之间的继承关系,实线的起始端是子类的UML图,终点端是父类的UML图,但终点端使用一个空心的三角形表示实线的结束。

例如:Animal类和它的两个子类Dog和Cat的UML图

关联关系

如果A类中的成员变量是用B类(接口)来声明的变量,那么A和B的关系是关联关系(Association),称A关联于B.。如果A关联于B,那么 UML 通过使用一个实线连A和B的UML图,实线的起始端是A 的UML图,终点端是B的UML图,但终点端使用一个指向B的UML图的方向箭头表示实线的结束。

例如:ClassRoom 类关联Light类的UML图。

 

依赖关系 

 如果A类中某个方法的参数用B类(接口)来声明的变量或某个方法返回的数据类型是B类型,则A和B的关系为依赖关系,A依赖于B。如果A依赖于B,UML通过使用一个虚线连接A和B的UML图,虚线的起始端是A的UML图,终点端是B的UML图,但终点端使用一个指向B的UML图的方向箭头表示虚线的结束。

ClassRoom依赖Student接口的UML图

 当需要强调A是通过方法参数依赖于B时,在UML图中使用虚线连接A和B的UML图

实现关系

如果一个类实现了一个接口,那么类和接口的关系是实现关系(Realization),称类实现接口。UML通过使用虚线连接类和它所实现的接口,虚线的起始端是类,虚线的终点端是它实现的接口,但终点端使用一个空心的三角形表示虚线的结束。

ClassOne和ClassTwo类实现 Create接口的UML图

 注释:

UML使用注释(Annotation)为类图提供附加的说明。UML在一个带卷角的长方形中显示给出的注释,并使用虚线将这个带卷角的长方形和它所注释的实体连接起来。

例如:Computer类的UML图,该UML图使用注释对Computer类中的add()方法

面向对象原则

抽象类和接口


抽象(abstract)类具有以下特点:
(1)抽象类中可以有abstract方法,也可以有非abstract方法。
(2)抽象类不能用new运算符创建对象。
(3)如果一个非抽象类是某个抽象类的子类,那么它必须重写父类的abstract方法 

(4)做上转型对象:尽管抽象类不能用new运算符创建对象,但它的非abstract子类必须重写它的所有abstract方法,可以让抽象类声明的对象成为其子类对象的上转型对象,并调用于类重写的方法。

例如:抽象类A中有一个abstract方法add(int x,int y)

A.java


public abstract class A {
	public abstract int add(int x,int y);
}

B.java

public class B extends A{
	public int add(int x,int y) {
		return x+y;
	}

}

Application.java

public class Application {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		A a;
		a = new B(); //a是B类对象的上转型对象
		int m = a.add(3, 2);//a调用子类B重写的add()方法
		System.out.println(m);
		
	}

}

接口

接口具有以下特点:

(1)接口中有public权限的abstract方法,default方法和static方法

(2)接口由类实现,及一个类实现一个接口,则它必须重写接口中的abstract方法。

(3)接口回调:接口回调指把实现接口的类的对象的引用赋给该接口声明的接口变量在,那么该接口变量就可以调用被类重写的接口方法。

下列接口Com中有一个abstract方法sub(int x,int y);

Com.java

public interface Com {
	public abstract int sub(int x,int y);
}

ComImp.java

 class ComImp implements Com {
	public int sub(int x,int y) {
		return x-y;
	}
}

Application.java

public class Application {
	public static void main(String args[]) {
		Com com;
		com = new ComImp();//com变量存放ComImp类的对象的引用
		int m = com.sub(8,2); //com回调ComImp类实现的接口方法
		System.out.println(m); //输出结果为6
	}

}

输出结果:6

面向抽象编程

当设计一个类时,让这个类面向抽象类或接口类,即类中的重要数据为抽象类或接口类声明的变量。

例如:已经有一个Circle类,该类创建的对象circle调用getArea()方法来计算圆的面积。

public class Circle {
	double r;
	Circle(double r){
		this.r = r;
	}
	public double getArea() {
		return(3.14*r*r);
	}

}
public class Pillar {
	double height;
	 Circle bottom; //将Circle对象作为成员,bottom是用具体类Circle声明的变量
	Pillar(Circle bottom,double height){
		this.bottom = bottom;
		this.height = height;
	}
	public double getVolume() {
		return bottom.getArea()*height;
	}
}

在上述Pillar类中,bottom是用具体类Circle声明的变量,如果不涉及用户需求的变化,上面Pillar类的设计没有什么不妥,但用户希望Pillar类能创建出底是三角形的柱体。上述Pillar类无法创建出这样的柱体,即上述设计的Pillar类不能应对用户这种需求。


重新设计Pillar类。首先,注意到柱体计算体积的关键是计算出底面积,一个柱体在计算底面积时不应该关心它的底是什么形状的具体图形,只应该关心这种图形是否具有计算面积的方法。因此,在设计Pillar类时不应当让它的底是某个具体类声明的变量。


下面将面向抽象重新设计Pillar类。首先编写一个抽象类Geometry(或接口),该抽象类 (接口)中定义了一个抽象的getArea()方法。

public abstract class Geometry { //如果使用接口,需用interface来定义Geometry
	public abstract double getArea();
}

现在Pillar类可以面向Geometry类编写代码,即Pillar类应当把Geometry对象作为自己的成员,该成员可以调用Geometry的子类重写的getArea()方法

Pillar类的设计不再依赖具体类,而是面向Geometry类,即Pillar类中的bottom是用抽象类Geometry声明的变量,而不是具体类声明的变量。

public class Pillar {
	double height;
	 Geometry bottom; //将Circle对象作为成员,bottom是用具体类Circle声明的变量
	Pillar(Geometry bottom,double height){
		this.bottom = bottom;
		this.height = height;
	}
	public double getVolume() {
		return bottom.getArea()*height; //bottom可以调用子类重写的getArea方法
	}
}
public class Retangle extends Geometry{
	double a,b;
	Retangle(double a,double b){
		this.a = a;
		this.b = b;
	}
	public double getArea() {
		return a*b;
	}
}
public class Circle extends Geometry {
	double r;
	Circle(double r){
		this.r = r;
	}
	public double getArea() {
		return(3.14*r*r);
	}

}
public class Application {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Pillar pillar;
		Geometry bottom;
		bottom = new Retangle(22,100);
		pillar = new Pillar(bottom,58);
		System.out.println("矩形底体积:"+pillar.getVolume());
		
		bottom = new Circle(10);
		pillar = new Pillar(bottom,60); //pillar是具有圆形底的柱体
		System.out.println("圆形底的体积:"+pillar.getVolume());
	}

}

运行结果:

矩形底体积:127600.0
圆形底的体积:18840.0

通过面向抽象来设计Pillar类,使得该Pillar类不再依赖具体类。,每当系统增加新的Geometry的子类时,例如增加一个Triangle子类,不需要修改Pillar类的任何代码,就可以使用Pillar创建出具有三角形底的柱体

“开-闭”原则

所谓“开-闭”原则(Open-Closed Principle),就是让用户的设计“对扩展开放,对修改关闭”。
本质是指当在一个设计中增加新的模块时,不需要修改现有的模块。在给出一个设计时,应当首先考虑用户需求的变化,将应对用户变化的部分设计为对扩展开放,而设计的核心部分是经过精心考虑之后确定下来的基本结构,这部分应当是对修改关闭的,即不能因为用户的需求变化而再发生变化。

例如,有4个类UML类图如图所示


该设计中的Geometry类和Pillar类就是系统中对修改关闭的部分,而Geometry的子类是对扩展开放的部分。当向系统再增加任何Geometry的子类时(对扩展开放),不必修改 Pillar类,就可以使用Pillar创建出具有Geometry的新子类指定底的柱体。
 

方法复用的两种技术:类继承和对象组合

子类继承父类的方法作为自己的一个方法,可以被子类中自己声明的任何实例方法调用。

父类的方法可以被子类以继承的方式复用。
通过继承来复用父类方法的优点是,子类可以重写父类的方法,即易于修改或扩展那些被
复用的方法。


通过继承复用方法的缺点如下:
(1)子类从父类继承的方法在编译时刻就确定下来了,所以无法在运行期间改变从父类继承的方法的行为。
(2)子类和父类的关系是强耦合关系,当父类的方法的行为更改时,必然导致
子类发生变化。
(3)通过继承进行复用也称“白盒”复用,其缺点是父类的内部细节对于子类而言是可见的。

组合复用

一个类可以把对象作为自己的成员变量,如果用这样的类创建对象,那么该对象中就会有其他对象,该对象将其他对象作为自己的组成部分(这就是人们常说的Has-A),或者说该对象是由几个对象组合而成。
如果一个对象a组合了对象b,那么对象a就可以委托对象b调用其方法,即对象a以组合的方式复用对象b的方法。


通过组合对象来复用方法的优点是:
(1)通过组合对象来复用方法也称“黑盒”复用,因为当前对象只能委托所包含的对象调用其方法,当前对象所包含的对象的方法的细节对当前对象是不可见的。
(2)对象与所包含的对象属于弱耦合关系,因为如果修改当前对象所包含的对象的类的代码,不必修改当前对象的类的代码。
(3)当前对象可以在运行时刻动态指定所包含的对象,如Pillar组合的bottom对象,可以在运行时刻指定bottom是Circle 对象或 Rectangle 对象。
 

下面模拟汽车动态更换驾驶员

person.java

public abstract class Person {
	public abstract String getMess();
}

 Car.java

public class Car {
	Person person; //组合驾驶员
	public void setPerson(Person p) {
		person = p;
	}
	public void show() {
		if(person == null) {
			System.out.println("没人驾驶");
		}
		else {
			System.out.println("驾驶人是:");
			System.out.println(person.getMess());
		}
	}
}

MainClass.java

public class MainClass {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Car car = new Car();
		int i=1;
		while(true) {
			try {
				car.show();
				Thread.sleep(2000); //每个2000毫秒更换驾驶员
				Class<?>cs = Class.forName("Driver"+i); 
				Person p = (Person)cs.getDeclaredConstructor().newInstance(); //无人驾驶或当期驾驶员继续驾驶
				car.setPerson(p); //更换驾驶员
				i++;
			}catch(Exception exp) {
				i++;
			}
			if(i>10) i=1;
		}
	}

}

继续编辑Person类子类程序,子类名字必须是Drive1,Drive2。。。。

public class Driver1 extends Person{
	public String getMess() {
		return "我是美国驾驶员";
	}
}
public class Driver5 extends Person {
	public String getMess() {
		return "我是女驾驶员";
	}
}

运行结果:

没人驾驶
驾驶人是:
我是美国驾驶员
驾驶人是:
我是美国驾驶员
驾驶人是:
。。。。

猜你喜欢

转载自blog.csdn.net/m0_46965984/article/details/124446386