3.1设计模式概念
设计模式(Design Patterns)是软件工程中常用的一种思想,它代表了在特定场景下解决常见问题的最佳实践。设计模式不是一种可以直接应用于代码中的“成品”,而是一种描述如何组织类和对象以及它们之间关系的高层次抽象。它们被广泛地应用于提高代码的可重用性、可维护性、可读性和可扩展性。
设计模式主要分为三大类:创建型模式(Creational Patterns)、结构型模式(Structural Patterns)和行为型模式(Behavioral Patterns)。
- 创建型模式:主要用于对象的创建过程。它隐藏了创建逻辑,而不是通过指定具体类来实例化对象。这样,系统的扩展和维护就变得更容易。常见的创建型模式包括:
- 工厂方法模式(Factory Method)
- 抽象工厂模式(Abstract Factory)
- 单例模式(Singleton)
- 建造者模式(Builder)
- 原型模式(Prototype)
- 结构型模式:关注于类和对象的组合。它采用不同方式将类或者对象组合在一起,以形成更大的结构。结构型模式使得我们可以将复杂系统分解成更小、更简单的部分,同时保持系统各部分之间的独立性。常见的结构型模式包括:
- 适配器模式(Adapter)
- 桥接模式(Bridge)
- 组合模式(Composite)
- 装饰器模式(Decorator)
- 外观模式(Facade)
- 享元模式(Flyweight)
- 代理模式(Proxy)
- 行为型模式:用于描述类或对象之间如何交互以及它们之间如何分配职责。行为型模式不仅描述对象或类的模式,还描述它们之间的通信模式。常见的行为型模式包括:
- 模板方法模式(Template Method)
- 命令模式(Command)
- 观察者模式(Observer)
- 状态模式(State)
- 策略模式(Strategy)
- 职责链模式(Chain of Responsibility)
- 迭代器模式(Iterator)
- 访问者模式(Visitor)
- 中介者模式(Mediator)
- 备忘录模式(Memento)
- 解释器模式(Interpreter)
使用设计模式时,重要的是要理解问题的本质和上下文,以便选择最合适的模式。虽然设计模式为解决问题提供了有用的框架,但过度使用或误用设计模式也可能导致代码变得复杂和难以理解。因此,在设计系统时,应根据具体情况灵活应用设计模式
3.2 软件可复用问题和面向对象设计原则
软件可复用问题和面向对象设计原则之间存在着紧密的联系。面向对象设计原则为软件复用提供了理论基础和实践指导,通过遵循这些原则,可以设计出更加灵活、可维护和可扩展的软件系统。以下是对软件可复用问题和面向对象设计原则的详细探讨:
一、软件可复用问题
软件复用是提高软件开发效率和质量的重要手段。它旨在通过重用已有的软件资源(如代码、设计、文档等)来减少重复劳动,加快开发速度,降低开发成本。然而,软件复用并非易事,它面临着多种挑战,如复用资源的可获取性、复用资源的适配性、复用资源的维护性等。
为了有效地解决软件复用问题,需要采取一系列措施,包括建立可复用的软件资源库、制定复用规范和标准、提供复用工具和平台等。同时,还需要在软件开发过程中遵循一定的设计原则,以确保设计出的软件系统具有良好的复用性。
二、面向对象设计原则
面向对象设计原则是一系列指导面向对象软件设计的准则,它们旨在帮助开发人员设计出高质量、可维护、可扩展的软件系统。这些原则包括但不限于以下几点:
-
单一职责原则(Single Responsibility Principle, SRP):一个类应该只负责一项职责。这个原则有助于降低类的复杂性,提高类的内聚性,从而使得类更加易于复用和维护。
-
开闭原则(Open-Closed Principle, OCP):软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。这个原则鼓励使用抽象和继承等机制来实现系统的可扩展性,同时减少因修改现有代码而引入的错误。
-
里氏代换原则(Liskov Substitution Principle, LSP):子类对象必须能够替换掉它们的基类对象被使用。这个原则确保了基类和子类之间的正确关系,使得子类可以无缝地替换基类而不会破坏系统的正确性。
-
依赖倒转原则(Dependency Inversion Principle, DIP):高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。这个原则促进了软件系统的解耦,提高了系统的灵活性和可维护性。
-
接口隔离原则(Interface Segregation Principle, ISP):不应该强迫客户依赖于它们不使用的方法。这个原则鼓励使用细粒度的接口来降低类之间的耦合度,从而提高系统的复用性和可维护性。
-
合成/聚合复用原则(Composite/Aggregation Reuse Principle, CARP):要尽量使用合成/聚合而非继承来达到复用目的。这个原则强调了通过组合和聚合关系来实现对象的复用,从而避免了继承带来的复杂性和耦合性。
-
迪米特法则(Law of Demeter, LoD):一个对象应该对其他对象保持最少的了解。这个原则有助于降低类之间的耦合度,提高系统的独立性和可维护性
在src下创建factory包,在此包中定义负责创建NewsDao实例的工厂类。关键代码如下。
packge factory
import dao.NewsDao;
import dao.impl.NewsDaoImpl;
/**
* 创建NewsDao实例的工厂类
*/
public class SimpleDaoFactory {
/**
* 创建NewsDao实例的工厂方法
*/
public static NewsDao getInstance(String key){
// return new NewsDaoImpl();
}
}
所展示的是简单工厂模式,又叫做静态工厂方法模式,他不属于Gof的23种设计模式之一,简单工厂模式是工厂模式家族中最简单的一种模式,可以理解为工厂模式的一个特殊实现。
在NewsServiceImpl中通过SimpleDaoFactory与NewsDaoImpl解耦合。关键代码如下
public class NewsServiceImpl implements NewsService{
// 所依赖的NewsDao对象
private NewsDao dao = SimpleDaoFactory.getInstance();
public void addNews(News news){
// 调用NewsDao的方法保存新闻信息
dao.save(news);
}
}
或者根据依赖倒置原则,使用setter方法传递依赖关系,关键代码如下。
public class NewsServiceImpl implements NewsService {
private NewsDao dao;
@Override
public void save(News news) {
this.dao=dao;
}
public void addNews(News news){
dao.save(news);
}
}
public class NewsServiceImplTest {
@Test
public void addNews() throws Exception{
AbstractFactory factory = new MySqlDaoFactory();
NewsDao dao = factory.getInstance();
NewsServiceImpl service = new NewsServiceImpl();
service.setDao(dao);
News news = new News();
news.setNtitle("测试标题4");
news.setNcontent("测试内容4");
service.save(news);
}
}
在简单工厂模式中,根据String类型参数创建NewsDao接口的不同实现类
关键代码如下
public class SimpleDaoFactory {
/**
* 创建NewsDao实例的工厂方法
*/
public static NewsDao getInstance(String key){
// return new NewsDaoImpl();
switch (key){
case "mysql":
return new NewsDaoMySqlImpl();
case "oracle":
return new NewsDaoOracleImpl();
case "redis":
return new NewsDaoRedisImpl();
default:
throw new RuntimeException("无效的数据库类型:"+key+",DAO获取失败");
}
}
}
在测试方法中,通过key获取指定NewsDao实例,改变key值可获得不同的NewsDao实例
public class NewsServiceImplTest {
@Test
public void addNews() throws Exception{
AbstractFactory factory = new MySqlDaoFactory();
NewsDao dao = factory.getInstance();
NewsServiceImpl service = new NewsServiceImpl();
service.setDao(dao);
News news = new News();
news.setNtitle("测试标题4");
news.setNcontent("测试内容4");
service.save(news);
}
}
3.3.3 代理模式
代理模式(Proxy Pattern)是程序设计中的一种设计模式,其核心概念在于为其他对象提供一种代理以控制对这个对象的访问。代理模式通过引入一个代理对象,来控制对真实对象的访问,可以在不修改真实对象代码的前提下,增加一些额外的操作或功能,如权限校验、日志记录、缓存等。
一、代理模式的基本结构
代理模式主要涉及两个角色:代理角色和真实角色。
- 代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。代理对象可以在客户端和目标对象之间起到中介的作用,保护目标对象,同时增强目标对象的功能。
- 真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。真实角色就是实现实际的业务逻辑,不用关心其他非本职责的事务。
二、代理模式的实现方式
代理模式可以通过静态代理和动态代理两种方式实现。
- 静态代理:由程序员创建或工具生成代理类的源码,再编译代理类。静态代理的代理类和委托类的关系在运行前就确定了,代理类的字节码文件在程序运行前就已经存在。
- 动态代理:动态代理是在实现阶段不用关心代理类,而在运行阶段才指定哪一个对象被代理。Java中的JDK动态代理是动态代理的一种实现方式,它可以在运行时动态生成一个代理对象,代理对象实现和原始类一样的接口,并将方法调用转发给被代理对象,同时还可以在方法调用前后执行额外的增强处理。
三、代理模式的使用场景
代理模式的应用场景非常广泛,主要包括以下几种情况:
- 远程代理:当对象位于远程服务器上时,可以使用代理模式来进行远程访问。代理对象可以隐藏实际对象的细节,客户端可以通过代理对象来访问远程对象,而无需了解远程的实现细节。
- 虚拟代理:当创建一个对象需要很长的时间或资源时,可以使用代理模式来延迟对象的创建。代理对象起初会创建一个简单的占位符对象,当需要时才真正创建真正的对象。
- 安全代理:当需要控制对对象的访问权限时,可以使用代理模式。代理对象可以控制客户端对真实对象的访问权限,比如实现某些操作的权限控制。
- 缓存代理:当需要缓存对象的计算结果时,可以使用代理模式。代理对象可以在调用真实对象之前首先检查缓存中是否存在结果,如果存在则直接返回缓存结果,避免重复计算。
- 日志记录代理:当需要对对象的方法调用进行日志记录时,可以使用代理模式。代理对象可以在调用真实对象的方法之前和之后进行一些额外的操作,比如记录日志。
四、代理模式的优缺点
优点:
- 将代理对象与真实被调用目标对象分离,降低系统耦合性。
- 可以起到保护目标对象的作用。
- 可以增强目标对象的功能。
- 扩展性好,可以在不修改真实对象的情况下增加额外的功能。
缺点:
- 会造成系统设计中类的数量增加。
- 在客户端和目标对象中增加一个代理对象,可能会导致请求速度变慢。
- 增加了系统的复杂度。
总的来说,代理模式是一种非常灵活和强大的设计模式,可以在多种场景下应用,以提高软件系统的可维护性、可扩展性和复用性。