类的六大关系

  1. 依赖关系

    A类用到B类

    • 可以是B类的成员的变量
    • 可以是B类的方法作为返回类型
    • 可以是接收B类的参数类型

    类图:

    image-20211002100539604
  2. 泛化关系

    也就是继承关系,是依赖关系的特例

    类图:

    image-20211002101321117
  3. 实现关系

    是依赖关系的特例

    类图:

    image-20211002101647231
  4. 关联关系

    • 关联关系实际上就是类与类之间的联系,也是依赖关系的特例
    • 具有导航性:即双向关系或单向关系
    • 具有多重性

    单向一对一关系

    1
    2
    3
    4
    5
    public class Person {
    private IDCard card;
    }

    public class IDCard {}

    类图:

    image-20211002102156162

    双向一对一关系

    1
    2
    3
    4
    5
    6
    7
    public class Person {
    private IDCard card;
    }

    public class IDCard {
    private Person person;
    }

    类图:

    image-20211002102354018
  5. 聚合关系

    • 表示整体和部分关系,整体和部分可以分开
    • 是关联关系的特例
    • 具有导航性和多重性
    1
    2
    3
    4
    5
    6
    7
    public class Person {
    private Hat hat;//帽子和人可以分开
    private Glasses glasses;//眼镜可以和人分开
    }

    public class Hat {}
    public class Glasses {}

    类图:

    image-20211002103042534
  6. 组合关系

    • 整体和部分关系,但是整体和部分不能分离
    1
    2
    3
    4
    5
    6
    7
    public class Person {
    private IDCard card;
    private Head head = new Head();
    }

    public class IDCard {}
    public class Head {}

    类图:

    image-20211002104230988

OOP七大原则

  1. 开闭原则:对扩展开放,对修改关闭(不修改原有的代码)
  2. 里氏代换原则:继承必须确保超类所拥有的性质在子类中仍然成立(不改变父类原有的功能)
  3. 依赖倒置原则:要面向接口编程,不要面向实现编程
  4. 单一职责原则:控制类的粒度大小,将对象解耦,提高其内聚性(一个对象尽量负责一个功能)
  5. 接口隔离原则:要为各个类建立它们需要的专用接口
  6. 迪米特法则:只与你的直接朋友交谈,不跟“陌生人”说话
  7. 合成复用原则:尽量先使用组合或者聚合等关联关系实现,其次才考虑使用继承关系来实现

入门热身

类的六种关系

  • 依赖关系、关联关系、聚合关系、组合关系、泛化关系、实现关系

类图表示为:

依赖关系

image-20210921095337185

关联关系

image-20210921095650033

聚合关系

image-20210921095734604

组合关系

image-20210921095814304

泛化关系

image-20210921095853495

实现关系

image-20210921095940507

案例应用

问题一:

依赖倒置原则在“顾客购物程序”中的应用。如顾客类的shopping(ShaoguanShop shop)方法只访问韶关网店,如果该顾客想从另外一家商店(如:婺源网店WuyuanShop)购物,就要修改该方法的参数类型,这违背了“依赖倒置”原则。解决方法是:定义一个商店接口Shop,顾客类面向该接口编程。

类图:

image-20210921100602367

代码:

接口:

1
2
3
public interface Shop {
public String sell();
}

类:

1
2
3
4
5
6
public class ShaoguanShop implements Shop {
@Override
public String sell() {
return "韶关土特产:香菇,木耳。。。。";
}
}
1
2
3
4
5
6
public class WuyuanShop implements Shop {
@Override
public String sell() {
return "婺源土特产:绿茶、酒槽鱼。。。。。";
}
}
1
2
3
4
5
public class Customer {
public void shopping(Shop shop){
System.out.println(shop.sell());
}
}
1
2
3
4
5
6
7
8
public class Test {
public static void main(String[] args) {
Customer customer = new Customer();
System.out.println("顾客购买一下商品:");
customer.shopping(new ShaoguanShop());
customer.shopping(new WuyuanShop());
}
}

运行结果:

1
2
3
顾客购买一下商品:
韶关土特产:香菇,木耳。。。。
婺源土特产:绿茶、酒槽鱼。。。。。

问题二:

迪米特法则的应用实例—-明星与经纪人的关系实例。明星由于全身心投入艺术,所以许多日常事务由经纪人负责处理,如:与粉丝的见面会,与媒体公司的业务洽淡等。这里的经纪人是明星的朋友,而粉丝和媒体公司是陌生人,所以适合使用迪米特法则,下边是其类图与运行结果,请写出程序代码:

类图:

image-20210921102037264

代码:

1
2
3
4
5
6
7
8
9
10
11
public class Star {
private String name;

public Star(String name) {
this.name = name;
}

public String getName() {
return name;
}
}
1
2
3
4
5
6
7
8
9
10
11
public class Fans {
private String name;

public Fans(String name) {
this.name = name;
}

public String getName() {
return name;
}
}
1
2
3
4
5
6
7
8
9
10
11
public class Company {
private String name;

public Company(String name) {
this.name = name;
}

public String getName() {
return name;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Agent {
private Star myStar;
private Fans myFans;
private Company myCompany;

public void setMyStar(Star myStar) {
this.myStar = myStar;
}

public void setMyFans(Fans myFans) {
this.myFans = myFans;
}

public void setMyCompany(Company myCompany) {
this.myCompany = myCompany;
}

public void meeting(){
System.out.println(myFans.getName()+"与明星"+myStar.getName()+"见面了");
}

public void business(){
System.out.println(myCompany.getName()+"与明星"+myStar.getName()+"洽谈业务");
}
}
1
2
3
4
5
6
7
8
9
10
public class Test {
public static void main(String[] args) {
Agent agent = new Agent();
agent.setMyFans(new Fans("粉丝韩庚"));
agent.setMyStar(new Star("林心如"));
agent.setMyCompany(new Company("中国传媒有限公司"));
agent.meeting();
agent.business();
}
}

测试结果:

1
2
粉丝韩庚与明星林心如见面了
中国传媒有限公司与明星林心如洽谈业务

简单工厂模式

概念:

定义一个工厂类,它可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类

类图:

image-20210921110840927

小demo:

抽象类:

1
2
3
public interface Car {
public void name();
}

类:

1
2
3
4
5
6
public class Aima implements Car {
@Override
public void name() {
System.out.println("爱玛电动车");
}
}
1
2
3
4
5
6
public class Yadea implements Car {
@Override
public void name() {
System.out.println("雅迪电动车");
}
}

工厂类:

1
2
3
4
5
6
7
8
9
10
11
public class Factory extends Aima {
public static Car getCar(String name){
if (name.equals("爱玛")){
return new Aima();
}else if (name.equals("雅迪")){
return new Yadea();
}else {
return null;
}
}
}

客户端:

1
2
3
4
5
6
7
8
9
public class Client {
public static void main(String[] args) {
Car car = Factory.getCar("爱玛");
car.name();
}
}
=======
测试结果:
爱玛电动车

弊端:不符合开闭原则,如果增加产品需要修改内部代码

工厂模式

概念:

定义一个用于创建对象的接口,但是让子类决定将哪一个类实例化。工厂方法模式让一个类的实例化延迟到其子类目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类

案例:用工厂方法设计电动自行车工厂的模拟程序。为每种品牌的电动自行车提供一个子工厂,如爱玛工厂专门负责生产爱玛(Aima)牌电动自行车,雅迪工厂专门负责生产雅迪(Yadea)牌电动自行车。如果今后需要生产台铃(Tailg)牌电动自行车,只需要增加一个新的台铃电动自行车工厂即可,无须修改原有代码,使得整个系统具有更强的灵活性和可扩展性。

类图:

image-20210921111657143

代码:

接口:

1
2
3
4
//生产工厂
public interface CarFactory {
public Car getCar();
}
1
2
3
4
//购买工厂
public interface Car {
public void name();
}

类:

1
2
3
4
5
6
7
public class AimaFactory implements CarFactory {

public Car getCar() {
System.out.println("爱玛电动车生产好了");
return new Aima();
}
}
1
2
3
4
5
6
public class YadiFactory implements CarFactory {
public Car getCar() {
System.out.println("雅迪电动车生产好了");
return new Yadi();
}
}
1
2
3
4
5
public class Aima implements Car {
public void name() {
System.out.println("欢迎购买爱玛电动车");
}
}
1
2
3
4
5
public class Yadi implements Car {
public void name() {
System.out.println("欢迎购买雅迪电动车");
}
}
1
2
3
4
5
6
7
8
9
10
11
public class Client {
public static void main(String[] args) {
CarFactory aimaFactory = new AimaFactory();
Car car = aimaFactory.getCar();
car.name();
}
}
============
测试结果:
爱玛电动车生产好了
欢迎购买爱玛电动车

优点:

工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节

能够让工厂自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部

在系统中加入新产品时,完全符合开闭原则

缺点:

系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,会给系统带来一些额外的开销

增加了系统的抽象性和理解难度

抽象工厂模式

概念:

  • 一个工厂可以生产一系列产品(一族产品),极大减少了工厂类的数量
  • 当系统所提供的工厂生产的具体产品并不是一个简单的对象,而是多个位于不同产品等级结构、属于不同类型的具体产品时就可以使用抽象工厂模式
  • 抽象工厂模式是所有形式的工厂模式中最为抽象和最具一般性的一种形式

案例:

用抽象工厂模式设计农场类

​ 要求:用抽象工厂模式设计两个农场,一个是昭关(SRfarm)农场用于养牛和种菜,一个是上饶(SGfarm)农场用于养马和种水果,可以在以上两个农场中定义一个生成动物的方法newAnimal()和一个培养植物的方法newPlant(),定义马类、牛类、蔬菜类和水果类等具体产品类,并定义一个show()方法输出马、牛、蔬菜和水果字样。客户端程序通过对象生成器类ReadXML读取XML配置文件中的数据来决定养什么动物和培养什么植物。运行结果如下:

image-20210921113914958

代码:

接口:

1
2
3
4
public interface Farm {
public Animal newAnimal();
public Plant newPlant();
}
1
2
3
public interface Animal {
public void show();
}
1
2
3
public interface Plant {
public void show();
}

类:

1
2
3
4
5
public class Cow implements Animal {
public void show() {
System.out.println("动物:牛");
}
}
1
2
3
4
5
public class Fruit implements Plant {
public void show() {
System.out.println("植物:水果");
}
}
1
2
3
4
5
public class Horse implements Animal {
public void show() {
System.out.println("动物:马");
}
}
1
2
3
4
5
public class Vegetable implements Plant {
public void show() {
System.out.println("植物:蔬菜");
}
}
1
2
3
4
5
6
7
8
9
10
11
public class SGFarm implements Farm {
public Animal newAnimal() {
System.out.println("新马出生");
return new Horse();
}

public Plant newPlant() {
System.out.println("水果长成");
return new Fruit();
}
}
1
2
3
4
5
6
7
8
9
10
11
public class SRFarm implements Farm {
public Animal newAnimal() {
System.out.println("新牛出生");
return new Cow();
}

public Plant newPlant() {
System.out.println("蔬菜长成");
return new Vegetable();
}
}
1
2
3
4
5
6
7
8
9
10
11
public class Client {
public static void main(String[] args) {
Farm sgFarm = new SGFarm();
// Animal animal = sgFarm.newAnimal();
// animal.show();
// Plant plant = sgFarm.newPlant();
// plant.show();
sgFarm.newAnimal().show();
sgFarm.newPlant().show();
}
}

优点:

隔离了具体类的生成

当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象

增加新的产品族很方便符合开闭原则

缺点:

增加新的产品等级结构麻烦,需要对原有系统进行较大的修改,甚至需要修改抽象层代码,这显然会带来较大的不便,违背了开闭原则

原型模式

概念:

  • 使用原型实例指定待创建对象的类型,并且通过复制这个原型来创建新的对象
  • 通过克隆方法所创建的对象是全新的对象,它们在内存中拥有新的地址
  • 通过不同的方式对克隆对象进行修改以后,可以得到一系列相似但不完全相同的对象

浅克隆与深克隆

  • 浅克隆(Shallow Clone):当原型对象被复制时,只复制它本身和其中包含的值类型的成员变量,而引用类型的成员变量并没有复制
  • 深克隆(Deep Clone):除了对象本身被复制外,对象所包含的所有成员变量也将被复制

案例:

开发一个应用“原型模式”的程序实例——用原型模式生成“三好学生”奖状。同一学校的“三好学生”奖状除了获奖人姓名不同,其他都相同,属于相似对象的复制,可以用原型模式创建,然后再做简单修改就可以了

类图:

img

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Citation implements Cloneable {
private String name;
private String info;
private String college;


public Citation(String name, String info, String college) {
this.name = name;
this.info = info;
this.college = college;
}
public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public void display(){
System.out.println("姓名:"+name+",奖状:"+info+",学校:"+college);
}

@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ProtoTypeCitation {
public static void main(String[] args) throws CloneNotSupportedException {
Citation c1 = new Citation("张三","三好学生","广软");
c1.display();
Citation clone = (Citation) c1.clone();
clone.setName("李四");
clone.display();

}
}
==============
测试结果:
姓名:张三,奖状:三好学生,学校:广软
姓名:李四,奖状:三好学生,学校:广软

优点:

  • 简化对象的创建过程
  • 扩展性较好
  • 提供了简化的创建结构
  • 可以使用深克隆的方式保存对象的状态

缺点:

  • 需要为每一个类配备一个克隆方法
  • 实现深克隆时需要编写较为负载的代码

单例模式

概念:

  • 确保一个类只有一个实例,并提供一个全局访问点来访问这个唯一实例。

要求:

  • 某个类只能有一个实例
  • 必须自行创建这个实例
  • 必须自行向整个系统提供这个实例

饿汉单例模式:

无须考虑多个线程同时访问的问题;调用速度和反应时间优于懒汉式单例;资源利用效率不及懒汉式单例;系统加载时间可能会比较长

懒汉单例模式:

实现了延迟加载;必须处理好多个线程同时访问的问题;需通过双重检查锁定等机制进行控制,将导致系统性能受到一定影响

类图:

img

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Visitor {
private static Visitor visitor = new Visitor();
private static int num = 0;

public Visitor() {
}

public static Visitor getVisitor() {
num++;
System.out.println("欢迎用户光临本站!");
return visitor;
}
public int getNum() {
return Visitor.num;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Client {
public static void main(String[] args) {
Visitor v1, v2;
v1 = Visitor.getVisitor();
v2 = Visitor.getVisitor();
int num = v2.getNum();
System.out.println("总的访问人数是:"+num);
}
}
================
欢迎用户光临本站!
欢迎用户光临本站!
2

案例:

开发一个应用“单例模式”的程序实例——用懒汉式单例模式模拟产生美国当今总统对象。在每一届任期内,美国的总统只有一人,所以本实例适合用单例模式实现,下边是用懒汉式单例实现的结构图

img

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class President {
private static volatile President instance = null;

public President() {
}


public static President getInstance() {
if (instance == null)
synchronized (President.class) {
if (instance == null) {
instance = new President();
System.out.println("产生了一个总统");
}
}
else {
System.out.println("已经有一个总统了,不能产生新总统!");
}
return instance;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class SingletonLazy {
public static void main(String[] args) {
President instance = President.getInstance();
President instance1 = President.getInstance();
System.out.println("判断是否是单例模式");
if (instance == instance1){
System.out.println(true);
}
}
}
=============
测试结果:
产生了一个总统
已经有一个总统了,不能产生新总统!
判断是否是单例模式
true

优点:

  • 提供了对唯一实例的受控访问
  • 可以节约系统资源,提高系统的性能
  • 允许可变数目的实例(多例类)

缺点:

  • 扩展困难(缺少抽象层)
  • 单例类的职责过重
  • 由于自动垃圾回收机制,可能导致共享的单例对象的状态丢失

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!