吉林大学设计模式第三次作业(上)

找到这里的多半是我的学弟学妹了,怎么说呢,希望能帮到你们吧,也更希望我留在这里的思路和代码是你们的下限,设计模式真的很精妙,祝你们有所增益。


题目一

请根据下面的对象适配器图画出与之相对应的类适配器类图

在这里插入图片描述

这道题首先要明晰适配器模式
适配器模式是干嘛的呢,它是把两个不能兼容的东西联系到一起的,《大话设计模式》里的例子是这样的:
姚明去NBA集训,但他才去的时候听不懂教练说英文,教练也不熟悉中文,那么这个时候雇一个随身翻译就好了,在这个场景里,随身翻译就是适配器,ta解决了姚明和教练二者无法沟通而工作的问题。

然后复习一下定义:

在这里插入图片描述
 由图可知适配器模式包含三个角色:

  • 1:Target(目标抽象类):目标抽象类定义客户所需的接口,可以是一个抽象类或接口,也可以是具体类。在类适配器中,由于C#语言不支持多重继承,所以它只能是接口。
  • 2:Adapter(适配器类):它可以调用另一个接口,作为一个转换器,对Adaptee和Target进行适配。它是适配器模式的核心。
  • 3:Adaptee(适配者类):适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配,适配者类包好了客户希望的业务方法。

然后这道题它的重点在于将对象适配器转化为类适配器,而这两者的区别就是

在这里插入图片描述

  • 类适配器的重点在于类**(继承实现)**,像上图中,是通过继承Adaptee类来实现适配器Adapter的功能;
  • 对象适配器的重点在于对象**(组合实现)**,像下图中,是通过在直接包含Adaptee类来实现的,当需要调用特殊功能的时候直接使用Adapter中包含的那个Adaptee对象来调用特殊功能的方法即可。

在这里插入图片描述

在这里插入图片描述
然后在这张作业图里面,我们可以看到B1B2B3都是继承于B,B继承于IB,原先跟Adater的关系是聚合,类B1、B2、B3为适配者Adaptee;Adapter继承于IA;B在这里通过被其他类包含而实现的,所以这张图是对象适配器。
所以修改这张图就是把适配器跟Adaptee之间的关系改为继承。(其实我的理解是这样的,适配器模式往往是为了弥补当前系统的接口缺失临时加的,那么适配器Adapter一定是后出现的,那么它只能去继承之前的类,或者包含以前的类)我的答案就是:
在这里插入图片描述
总之就是要通过继承方式派生得到类适配器Adapter1、Adapter2、Adapter3,同时适配器


题目二

在一个画图的小程序中,你已经实现了绘制点(Point)、直线(Line)、方块(Square)等图形的功能。而且为了让客户程序在使用的时候不用去关心它们的不同,还使用了一个抽象类图形(Shape)来规范这些图形的接口(Draw)。现在你要来实现圆的绘制,这时你发现在系统其他的地方已经有了绘制圆的实现类(XCircle),但同时你又发现已实现类XCircle的绘制方法(DrawIt)和你在抽象类Shape中规定的方法名称(Draw)不一样!这可怎么办?
方案一:修改已实现类XCircle的方法名DrawIt为Draw,是否合适?为什么?
方案二:修改抽象类Shape的方法名Draw为DrawIt,是否合适?为什么?
方案三:请你给出其它的解决方法。

哈哈这道题一看就是用适配器嘛。
方案一不合适,因为需要去修改所有使用它的地方,这件事不可控。并且这样不符合开闭原则,修改已有的实现类可能会导致其他使用了本部分类的相关代码无法正常使用,影响系统其他功能。
方案二也不合适,也要去修改所有图形的实现方法以及已有的引用。也不符合开闭原则,修改抽象类将会导致客户端的相关代码也必须随之修改,否则将无法正常使用。
方案三:我的解决办法就是采用适配器模式(对象适配器)对原来的设计进行改造:新增加一个适配器类Adapter(),其含有已有的绘制圆实现类XCircle的对象xCircle,用于在Draw()中调用已有函数Drawlt()。此外Adapter()泛化自抽象类Shape,调用xCircle的函数实现了Draw()。这样即可实现调用已有的绘制圆的函数而不改动其他实现。
其设计的相关实现结构如下:
在这里插入图片描述

代码如下:

public class Circle extends Shape {
    
    
	private XCircle xcircle;
	public Circle() {
    
    
		xcircle = new XCircle();
	}
	public void draw() {
    
    
		xcircle.DrawIt();
	}
}

题目三

下面是文件夹复制功能的代码实现,请分析该实现存在什么问题,给出设计模式解决方案,画出类图,写出关键代码。
在这里插入图片描述

这道题要使用组合模式,因为文件夹和文件的管理是典型的树形结构(整体-局部),我当时对相关定义掌握不太好,画的UML图并不标准。

基于我自己对设计模式相关的理解的话,我觉得这道题可以这么改:

  • 语法问题:Folder类中申明的List容器对象list应该这么写——List<Folder> folders = new ArrayList();
  • 对于子类要重写的函数在父类中最好设置为abstract,这样编译器会提醒你重写防止你直接让子类延续父类的能力
  • 最关键的改动:文件实现不能直接继承文件夹的类,文件夹有的功能文件是没有的!所以这道题里文件和文件夹的相同属性才应该继承自同一个抽象父类或者实现某一个共同的接口,而不能图方便直接让文件夹和文件杂糅在一起(比如举个例子,文件夹里面可以放文件和文件夹,文件里面可以吗?文件有很多格式,文件夹有必要有很多格式吗?)
  • 仔细观察这道题,让文件有文件夹增加、删除文件的功能我觉得不合理,应该分开;同时他俩在copy函数调用的时候有不同的响应方式,这完全可以用多态实现;除此之外其实我不太理解File123是什么东西,我先理解为三种不同的文件格式吧,让他们都继承文件类File,为了降低继承带来的耦合,我在这里选择文件夹类Folder和文件File类分开写,然后让他俩实现同一个接口AbstractFile(这个抽象接口内定义了一个方法:copy())
    请添加图片描述
    代码如下:
//AbstractFile接口,实现后重写copy函数
public abstract interface AbstractFile {
    
    
	public abstract void copy();
	//相应的复制行为
}

//Folder文件夹类

import java.util.ArrayList;
import java.util.List;

public class Folder implements AbstractFile {
    
    
	public String name;
	private List <File> listfile = new ArrayList();
	private List <Folder> listfolder = new ArrayList();
	
	public Folder(String name) {
    
    
		this.name=name;
		}
	//该文件夹新增文件/文件夹
	public void add(File file) {
    
    
		listfile.add(file);
	}
	public void add(Folder folder) {
    
    
		listfolder.add(folder);
	}
	
	
	//该文件夹删除某个文件/文件夹
	public void remove(File file) {
    
    
		listfile.remove(file);
	}
	public void remove(Folder folder) {
    
    
		listfile.remove(folder);
	}
	
	//统计该文件夹中有多少个文件
	public double getCount() {
    
    
		return listfile.size()+listfolder.size();
	}
	
	//Folder特有业务实现
	public void operation() {
    
    }
	
	@Override
	public void copy() {
    
    
		
		for(int i=0;i<listfile.size();i++) {
    
    
			File file = (File)listfile.get(i);
			file.copy();
		}
		for(int i=0;i<listfolder.size();i++) {
    
    
			Folder folder = (Folder)listfolder.get(i);
			System.out.println("进行文件夹的复制,文件夹的名称为:"+folder.name);
			folder.copy();
		}
	}
}


//文件类File

public class File implements AbstractFile {
    
    
	public String name;
	
	public File(String name) {
    
    
		this.name=name;
	}
	public void copy() {
    
    
		System.out.println("进行文件的复制,文件的名称为:"+name);
	}

}

class File1 extends File{
    
    
	public File1(String name) {
    
    
		super(name);
		// TODO Auto-generated constructor stub
	}}

class File2 extends File{
    
    
	public File2(String name) {
    
    
		super(name);
		// TODO Auto-generated constructor stub
	}}
class File3 extends File{
    
    
	public File3(String name) {
    
    
		super(name);
		// TODO Auto-generated constructor stub
	}}


//客户端Client类
public class Client {
    
    
	public void show(Folder folder) {
    
    
		System.out.println("当前正在为"+folder.name+"进行复制操作,进度为:");
		folder.copy();
	}
	public static void main(String[] args) {
    
    
		Client c = new Client();
		Folder folder1 = new Folder("文件夹1");
		File file1 = new File("文件1");
		Folder folder2 = new Folder("文件夹2");
		File file2 = new File("文件2");
		File file3 = new File("文件3");
		folder1.add(file1);
		folder1.add(file2);
		folder1.add(folder2);
		folder2.add(file3);
        c.show(folder1);
	}

}



输出:
请添加图片描述


题目四

在开发超市收银系统时,最主要的功能是收银,就是顾客拿来一个商品,扫描商品的条形码,显示该商品的金额即可,但超市经常会遇到打折、赠送等活动,程序开发时要如何处理呢?因为收银的功能可能还会被别的程序所使用,所以不能直接在该方法上修改,该怎么解决这个问题呢?采用继承是解决这类问题的一个方法,代码如下:
在这里插入图片描述

如果要进行打折,则代码如下:

在这里插入图片描述

到目前为止,这样的解决方案没有问题,但在系统使用过程中,有的用户又有了新的要求,需要打折后,再减一毛钱,示意代码如下:
在这里插入图片描述

请分析上述实现之不足,给出设计模式解决方案,画出类图,写出关键代码。

这道题我们老师给的标准答案是装饰模式,老师给的解释是:
①需要考虑如果打折计划之间需要相互叠加的情况,比如:打折后满减,满减后打折,立减后打折,立减再打折再满减。这样强调的是功能的叠加和方便组合,而不再是替换。所以装饰模式更好。
我下面是我开始写的策略模式,emmm懒得删了

上面使用的是简单工厂模式,对于商场的每一种促销方案增加一种子类去实现,然后在方案工厂中对要用的方案进行调用,但不足就在于简单工厂模式只是解决了方案对象的创建问题,尽管最终的方案工厂包括了所有的收费方式,但是商场经常性的更改打折额度和返利额度,每次都需要维护或者扩展这个方案工厂,这是很糟糕的处理方式(这意味着简单工厂虽然能用,但一定不是最好的)

继续分析,商场收银这项活动,不管是打折还是返利,其实都是一种算法,而算法本身只是一种策略(这就让引导我们去使用策略模式),是算法的替换带来了变换点,而面向对象的一大目标就是封装变换点。

我们来看策略模式的定义:定义了一系列算法的方法,分别封装起来,让它们之间可以互相替换(相当于维护了一个通道,使得客户端可以用相同的方式调用任意算法,),减少了各种算法类与使用算法类之间的耦合。其包含——

  • 用Strategy类为Context定义了一系列可供重用的算法或行为
  • ConcreteStrategy封装了具体的算法和行为,继承Strategy
  • Context类用一个ConcreteStrategy来配置,维护一个对Strategy类的引用(是为了初始化的时候传入一个具体的策略对象)

再来看题目,显然三种类①不打折的Cash②打折的RebateCash③返利一毛钱BacktrackCash是三种给钱策略,我们将日后打折相关的类都叫做CashRebate、返利叫做CashReturn、正常收费CashNormal。这些收费类都有一个公共的功能那就是GetResult()得到计算费用的结果,以此就找到了算法之间的抽象父类,使得每个算法都有自己类,能够进行单独的接口测试,使用策略模式就能保证修改其中任何一个都不会对其他造成影响(相较于之前的简单模式就必须频繁改动方案工厂里的判别条件以生成对象)
UML类设计图:
请添加图片描述

核心代码:

//CashSuper算法父类以及实现算法
public abstract class CashSuper {
    
    
       public abstract double getCash(double money);
}

class CashNormal extends CashSuper{
    
    
	public double getCash(double money) {
    
    
		return money;
	}
}

class CashRebate extends CashSuper{
    
    
	private double rate;
	public CashRebate(double rate) {
    
    
		this.rate=rate;
	}
	public double getCash(double money) {
    
    
		return money*rate;
	}
}

class CashReturn extends CashSuper{
    
    
	private double rate;
	private double returnMoney;
	
	public CashReturn(double rate,double returnMoney) {
    
    
		this.rate=rate;
		this.returnMoney=returnMoney;
	}
	public double getCash(double money) {
    
    
		CashRebate cs = new CashRebate(rate);
		return cs.getCash(money)-returnMoney;
	}
}

//CashContext接口
public class CashContext {
    
    
	private CashSuper cs = null;
	
	public CashContext(String type) {
    
    
		//这一步是把简单工厂模式加入到策略模式里了
		//由字符串决定收费类型,调用对应的算法类
		switch(type) {
    
    
		case "正常收费" :
			CashNormal cs0 = new CashNormal();
			cs = cs0;
			break;
		case "打1折" :
			CashRebate cs1 = new CashRebate(0.1);
			cs = cs1;
			break;
		
		case "打1折后,再减一毛钱":
			CashReturn cs2 = new CashReturn(0.1,0.1);
			cs = cs2;
			break;
			
		}
	}
		
	public double GetResult(double money) {
    
    
			return cs.getCash(money);
		}
		
}

写完之后的感觉就是原先的简单工厂模式需要客户端认识CashSuper(收费手段)和CashFactory(收费工厂:进行判别实例化算法对象用的),现在客户端只用跟CashContext进行交互就行了。这样就降低了耦合虽然代码量增加不少


题目五

小王正在开发一套视频监控系统,考虑到Windows Media Player和Real Player是两种常用的媒体播放器,尽管它们的API结构和调用方法存在区别,但这套应用程序必须支持这两种播放器API,而且在将来可能还需要支持新的媒体播放器。请你针对上面的描述,帮助小王进行设计,给出设计思路及所采用的设计模式,画出类图,并写出关键代码。

这道题我们老师给的答案是:适配器模式。比较明显了,因为接口不兼容带来的问题。但我当时还是用了抽象工厂hhhh

分析这道题,对于原先的设计思路肯定是new WindowsMediaPlayer()以获取对应的功能,但这样就把实例化的对象框死了,所以我们要想办法能不能让子类去决定实例化哪一个媒体类,这就需要使用抽象工厂模式——

  • 提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类

把这道题进行抽象就是:我有个系统,这个系统运作需要一款媒体播放器,它想用哪个就实例化哪个播放器对象,好的主人公就出来了:

  • 设计一个Player接口工厂,用于系统访问,就解除了与具体媒体播放器的耦合
    • WindowsMediaPlayer是Player的一种实现,用于系统访问Windows Media Player播放器
    • RealPlayer是Player的另一种实现,用于系统访问Real Player播放器
      同时抽象工厂模式免不了的会需要在一个大工厂里记录所有媒体播放器的接口链接,就会导致switch-case泛滥成灾,为了弥补这个先天不足,可以采用“反射技术”:
using System.Reflection

Assembly.Load("程序集名称").CreateInstance("命名空间.类名称")

其原理为:

//常规写法
IUser result = new WindowsMediaPlayer();

//反射的写法
using System.Reflection
IUser result = (IUser)Assembly.Load("抽象工厂模式").CreateInstance("抽象工厂模式.WindowsMediaPlayer")

其实好处就在于,将原来的对象变成字符串变量,就很容易根据需要变更了。
综上所诉,我觉得选择抽象工厂模式是希望我们的系统调用跟创建实例过程分离,系统通过媒体播放器的抽象接口操纵实例,媒体播放器的具体类名也由工厂实现分离。
UML类图:

请添加图片描述

#include<iostream>
using System.Reflection;
using namespace std;

class Player{
    
    
	public:
	void show(){
    
    
		cout<<"null"<<endl;
	}
};

class RealPlayer : Player{
    
    
	public:
	void show(){
    
    
		cout<<"RealPlayer"<<endl;
	}
};

class WindowsMediaPlayer : Player{
    
    
	public: 
	void show(){
    
    
		cout<<"WindowsMediaPlayer"<<endl;
	}
};


class PlayerFactory {
    
    
	private:
	static readonly string AssemblyName = "抽象播放器工厂";
	//媒体播放器可以根据需要进行字符串的变更
	static readonly string player = "RealPlayer";
	
	public: 
	static Player CreatePlayer(){
    
    
		string classname = AssemblyName + "." + player;
		return (Player)Assembly.Load(AssemblyName).CreateInstance(classname);
	}
    
};

int main(void){
    
    
	Player p = new Player();
	PlayerFactory iu =PlayerFactory.CreatePlayer();
}

猜你喜欢

转载自blog.csdn.net/KQwangxi/article/details/120927198