有哪些设计模式及其优点
依赖注入原则(DIP)
控制反转(IoC) - 实现DIP的设计方法
各种设计模式实现IoC
依赖注入(DI)
各种类型实现DI
IoC容器如何有助于应用DI。
设计模式是一套经过验证的事实上的行业标准,也是最好的解决经常性问题的做法。设计模式不是现成的解决方案。相反,它们是实现和应用最佳方式的一种方式或模板解决您的问题。
使用设计模式有许多优点:
1.提高了软件的可重用性
2.开发周期变得更快
3.使代码更具可读性和可维护性
4.提高效率并增强整体软件开发
5.提供常用词汇来描述问题和最佳解决方案的一种更抽象的方式。
设计原则:一般来说,这是关于什么是正确的方式和什么的指导方针
是设计应用程序的错误方法。设计原则总是谈什么而不是如何做到这一点。
设计模式:针对常见问题的通用且可重用的解决方案。
设计模式谈论如何解决给定软件设计中的问题
提供明确的方法论。
控制反转
IoC是一种设计方法,用于通过将流程控制从主程序转换为其他实体或框架,在软件工程中构建松耦合系统。
依赖倒置原则
DIP提供高级指导,使您的代码松散耦合。 它说如下:
高级模块不应依赖于低级模块来履行其职责。 两者都应该取决于抽象。
抽象不应该依赖于细节。 细节应取决于抽象。
通过IoC实现DIP
DIP建议高级模块不应该依赖于低级模块。 两者都应该取决于抽象。 IoC提供了一种实现高级和低级模块之间抽象的方法。
让我们看看我们如何在资产负债表示例中通过IoC应用DIP。该基本设计问题是高层模块(资产负债表)紧紧依赖
在低级(获取和导出数据)模块上。我们的目标是打破这种依赖。 为实现这一目标,IoC建议反转控制。 在IoC中,可以通过以下方式实现反转控制:
反转接口:确保高级模块定义接口,低级模块遵循它。
反转对象创建:将依赖关系的创建从主模块更改为其他程序或框架
反转流程:更改应用程序流程.
ID容器
IoC容器
到目前为止,我们已经讨论过扮演依赖关系的代码或框架供应商。它可以是任何自定义代码或完整的IoC容器。一些开发人员将它称为DI容器,但我们只需将其称为容器。
如果我们编写自定义代码来提供依赖性,那么事情会变得更顺畅,直到我们有了单一依赖程度。以我们的客户端类也是的方案为例
依赖于其他一些模块。这会导致链接或嵌套依赖项。在这种情况下,实现依赖注入将变得非常复杂通过手动代码。这就是我们需要依赖容器的地方。
容器负责创建,配置和管理对象。您只需做配置,容器将负责对象实例化和依赖管理轻松。您不需要编写任何自定义代码
正如我们在使用工厂或服务定位器模式实现IoC时所写的那样。所以,作为开发者,你的生活很酷。你只是暗示你的依赖关系,并且容器将处理其余部分,您可以专注于实现业务逻辑。
如果我们选择容器来设置资产负债表模块的依赖关系,那么容器将首先创建所有依赖项的对象。然后,它将创建一个对象
资产负债表类,并传递其中的依赖项。容器将完成所有这些事情静静地给你提供资产负债表模块的所有对象在其中设置的依赖项。可以使用下图描述此过程:
使用DI容器的优点
管理依赖:
1. 从代码中隔离对象创建过程,使代码更加干净和可读。
2. 从客户端模块中删除对象连接(设置依赖关系)代码。容器将处理物体布线。
3. 使模块100%松耦合。
4.管理模块的整个生命周期。当您要为应用程序执行中的各种范围(例如请求,会话等)配置对象时,这非常有用。
5.交换依赖关系只是配置问题 - 代码中不需要进行任何更改。
6.它是处理对象生命周期和依赖关系管理的更集中的方式。当您想要在依赖项中应用一些通用逻辑时,这很有用,例如,Spring中的AOP。我们将在第6章,面向方面编程和拦截器中看到有关AOP的详细信息。
7. 您的模块可以受益于容器附带的高级功能。
3种依赖注入类型
在DI中,您需要在客户端对象中设置入口点,从中可以注入依赖项。 基于这些切入点,DI可以使用以下类型实现:
1.构造函数注入
2.Setting注射
3.接口注入
Spring,Google Guice和Dagger是目前可用的一些IoC容器。Java Enterprise Edition版本6开始,Java引入了Context依赖注入(CDI),企业中的依赖注入框架版。 它或多或少类似于Spring基于注释的DI实现。 在所有前面的容器中,Spring是当今最受欢迎和广泛使用的IoC容器。
对象创建的反转
通过服务定位器
服务定位器模式与工厂模式的工作方式大致相同。 服务定位器可以找到现有对象并将其发送到客户端,而不是每次都像工厂模式一样创建新对象。 我们将简要介绍服务定位器如何工作以创建对象,而不是详细介绍。 服务定位器的流程可以按照下图描述:
反转对象创建
一旦设置了模块之间的抽象,就没有必要保持逻辑
在更高级别的模块中创建依赖项对象。 让我们再举一个例子来理解对象创建设计的反演的重要性。
假设你正在设计一个战争游戏。 你的玩家可以使用各种武器射击敌人。 您为每个武器创建了单独的类(低级模块)。
在玩游戏时,您的玩家可以根据获得的积分添加武器。
此外,玩家可以更换武器。 为了实现接口的反转,我们
创建了一个名为Weapon的接口,它将由所有武器模块实现,如下图所示:
错误的方式如下:
假设你最初在游戏中保留了三种武器。 如果你保持玩家模块中的武器创建代码,选择武器的逻辑就是根据以下代码段:
public class Player {
private Weapon weaponInHand;
public void chooseWeapon(int weaponFlag){
if(weaponFlag == 1){
weaponInHand = new SmallGun();
}else if(weaponFlag ==2){
weaponInHand = new Rifle();
}else{
weaponInHand = new MachineGun();
}
} p
ublic void fireWeapon(){
if(this.weaponInHand !=null){
this.weaponInHand.fire();
}
}
}
由于玩家模块正在处理创建武器的对象,我们是在chooseWeapon()方法中传递一个标志。
让我们假设在一段时间内,你在游戏中添加了一些武器。 您最终会更改播放器的代码模板每次添加新武器。
正确的方式如下:
此问题的解决方案是从主体中反转对象创建过程模块到另一个实体或框架。
让我们首先将此解决方案应用于我们的播放器模块。 更新后的代码如下:
public class Player {
private Weapon weaponInHand;
public void chooseWeapon(Weapon setWeapon){
this.weaponInHand = setWeapon;
}
public void fireWeapon(){
if(this.weaponInHand !=null){
this.weaponInHand.fire();
}
}
}
您可以观察以下事项:
在chooseWeapon()方法中,我们将武器的对象传递给接口。 Player模块不再处理武器对象的创建。
这样,Player(更高级别)模块与Weapon完全分离(低级)模块。两个模块都通过接口进行交互,由更高级别的模块定义。对于添加到系统中的任何新武器,您无需更改任何内容在播放器模块中。
让我们将此解决方案(反转创建对象)应用于我们的资产负债表模块。该
BalanceSheet模块的更新代码将符合以下代码段:
public class BalanceSheet {
private IExportData exportDataObj= null;
private IFetchData fetchDataObj= null;
//Set the fetch data object from outside of this class.
public void configureFetchData(IFetchData actualFetchDataObj){
this.fetchDataObj = actualFetchDataObj;
}/
/Set the export data object from outside of this class.
public void configureExportData(IExportData actualExportDataObj){
this.exportDataObj = actualExportDataObj;
}
public Object generateBalanceSheet(){
List<Object[]> dataLst = fetchDataObj.fetchData();
return exportDataObj.exportData(dataLst);
}
}
以下是一些快速观察:
获取数据和导出数据模块的对象在资产负债表模块外部创建,并通过configureFetchData()和configureExportData()方法传递。
资产负债表模块现在100%与获取数据和导出数据模块分离
对于任何新类型的提取和导出数据,资产负债表模块不需要进行任何更改
此时,DIP和IoC之间的关系可以按照下图描述:
最后,我们通过IoC实现了DIP,解决了模块之间相互依赖的最基本问题之一。
但坚持下去,还有一些事情还没有完成。 我们已经看到了保持对象
远离主模块创建将消除容纳更改的风险并使代码解耦。 但我们还没有探讨如何创建和
将依赖项对象从外部代码传递到您的模块中。 有多种方法可以反转对象的创建。
接口注入
接口注入定义了依赖提供者应该与之交谈的方式
客户。 它抽象了传递依赖的过程。 依赖关系提供程序定义了所有客户端都需要实现的接口。 这种方法并不常用。
从技术上讲,界面注射和注射器注射是相同的。 他们都使用某种方法来注入依赖。 但是,对于接口注入,该方法由提供依赖性的对象定义。
让我们在我们的资产负债表模块中应用界面注入:
public interface IFetchAndExport {
void setFetchData(IFetchData fetchData);
void setExportData(IExportData exportData);
}
//Client class implements interface
public class BalanceSheet implements IFetchAndExport {
private IExportData exportDataObj= null;
private IFetchData fetchDataObj= null;
//Implements the method of interface injection to set dependency
@Override
public void setFetchData(IFetchData fetchData) {
this.fetchDataObj = fetchData;
}
//Implements the method of interface injection to set dependency
@Override
public void setExportData(IExportData exportData) {
this.exportDataObj = exportData;
}
public Object generateBalanceSheet(){
List<Object[]> dataLst = fetchDataObj.fetchData();
return exportDataObj.exportData(dataLst);
}
}
反转接口
反转接口意味着将交互控制从低级模块反转到高级模块。 您的高级模块应该决定哪些低级模块可以与之交互,
而不是不断改变自己以集成每个新的低级模块。
反转界面后,我们的设计将如下图所示:
在此设计中,资产负债表模块(高级)与获取数据交互,并使用通用接口导出数据(低级)模块。 这种设计的非常明显的好处是,您可以添加新的获取数据和导出数据(低级)模块,而无需更改资产负债表模块(高级别)上的任何内容。至于低级模块与接口兼容的高级模块很乐意与之合作。
使用这种新设计,高级模块不依赖于低级模块,而是通过抽象(接口)进行交互。 将接口与实现分离是实现DIP的先决条件。
public interface IFetchData {
//Common interface method to fetch data.
List<Object[]> fetchData();
}
public interface IExportData {
//Common interface method to export data.
File exportData(List<Object[]> listData);
}
public class FetchDatabase implements IFetchData {
public List<Object[]> fetchData(){
List<Object[]> dataFromDB = new ArrayList<Object[]>();
//Logic to call database, execute a query and fetch the data
return dataFromDB;
}
}
public class FetchWebService implements IFetchData {
public List<Object[]> fetchData(){
List<Object[]> dataFromWebService = new ArrayList<Object[]>();
//Logic to call Web Service and fetch the data and return it.
return dataFromWebService;
}
}
public class ExportHTML implements IExportData{
public File exportData(List<Object[]> listData){
File outputHTML = null;
//Logic to iterate the listData and generate HTML File
return outputHTML;
}
}
public class ExportPDF implements IExportData{
public File exportData(List<Object[]> dataLst){
File pdfFile = null;
//Logic to iterate the listData and generate PDF file
return pdfFile;
}
}
Finally, the balance sheet module needs to rely on interfaces to interact with low-level
modules. So the updated BalanceSheet module should look like the following snippet:
public class BalanceSheet {
private IExportData exportDataObj= null;
private IFetchData fetchDataObj= null;
public Object generateBalanceSheet(){
List<Object[]> dataLst = fetchDataObj.fetchData();
return exportDataObj.exportData(dataLst);
}
}
要克服这个问题,您需要将对象创建从更高级别的模块转换为其他实体或框架。 这是实现IoC的第二种方式。