- 浏览: 416640 次
- 性别:
- 来自: 上海
文章分类
- 全部博客 (161)
- 设计模式 (2)
- Debian (3)
- windows (9)
- ASP/.NET (1)
- 电脑常识 (5)
- BT5 (1)
- CentOS (7)
- Ubuntu (10)
- Linux (14)
- MySQL (17)
- excel (1)
- CMD (3)
- eclipse (11)
- thinkpad (1)
- Java (20)
- nokia (2)
- C++ (3)
- CSS (1)
- Struts2 (2)
- FreeMarker (2)
- JSLT (3)
- EL (2)
- 开源 (2)
- Tomcat (2)
- VMware (3)
- MyBatis (2)
- JS (3)
- SpringMVC (1)
- swfupload (1)
- Chrome (1)
- XAMPP (1)
- Oracle (7)
- beyond compare (1)
- maven (4)
- SVN (2)
- SQL (2)
- Git (4)
- Spring (1)
- VC (1)
- web界面 (2)
- Jquery (1)
- Hibernat (1)
- MSSQL (2)
- telnet (1)
- Thundbird (1)
- 办公 (1)
- Amazon EC2 (1)
- JPA (2)
- J2CMS (1)
- hadoop (8)
- redhat (2)
- 算法 (2)
- Hbase (1)
- hdfs (2)
- hadoop2 (1)
- yarn (1)
- redis (1)
- DBCP (1)
- Spark (1)
- mac (1)
- python (1)
- IntelliJ IDEA (1)
- hibernate (1)
- 数学 (1)
最新评论
-
java_feishen:
Eclipse 的 Window -> Preferen ...
Git Push错误“Error writing request body to server” -
851228082:
@Autowired private HttpServle ...
如何在SpringMVC中获取request对象 -
麦田里的阳光:
麦田里的阳光 写道是啊,这是一款很好用的软件,最新版本下载链接 ...
beyond compare -
麦田里的阳光:
是啊,这是一款很好用的软件,最新版本下载链接: http:// ...
beyond compare -
BigCat2013:
楼主上海哪里?我也是上海的。
Spark集群的安装与部署
聚合和耦合
Jeremy Miller
目录
降低耦合
提高聚合
消除不适当的亲密
Demeter 定律
只是告知,不要询问
只说一次
总结
很多软件设计一直都存在一个问题:这段代码应放置在哪里?我一直在寻找编排代码的最佳方法,以便能够更轻松地编写、理解代码,并在以后更方便地进行更改。如果我的代码构造很漂亮,我将可名扬四海,无限荣光。如果构造得很糟糕,那些追随我的开发人员会一直对我埋怨不停。
我特别想在我的代码结构方面实现三个具体目标:
让代码中需要一起更改的部分尽可能靠近在一起。
允许代码中不相关的部分独立更改(也称为“正交性”)。
最大程度减少代码中的重复部分。
要实现这三个目标,我需要一些工具来协助我了解应将新代码放置到什么位置,并借助其他一些工具帮助我识别出是否将代码放置到了错误位置。
大体上说,这些目标都与聚合和耦合这两个典型代码质量紧密相关。我通过达到更高程度的聚合和更松散的耦合实现这些目标。当然,我们首先需要了解这些质量的含义以及为何聚合和耦合是有用的概念。然后我想讨论一些我将称之为“设计矢量”的内容,它们可以帮助我们实现更佳的结构,并帮助我们认识到何时需要丢弃那些已经不知不觉在我们代码中形成的错误结构。
补充一点,我是名为 StructureMap 的开源控制反转 (IOC) 工具的主要开发人员。我将使用源自 StructureMap 的一些现实示例来说明这些矢量所针对的设计问题。换言之,千万不要再犯我已犯过的错误。
降低耦合
在软件设计的几乎所有讨论场合,你都会随时听到“松散耦合”或“紧密耦合”这样的术语。类或子系统之间的耦合是这些类或子系统之间互联程度的量度标准。紧密耦合表示相关的类必须了解彼此的内部细节、更改将波及整个系统,并且系统可能更难以了解。图 1 显示了一个精心设计的业务处理模块示例,该模块与除业务逻辑之外的其他各种关注问题紧密耦合。
图 1 紧密耦合的代码
复制代码
public class BusinessLogicClass {
public void DoSomething() {
// Go get some configuration
int threshold =
int.Parse(ConfigurationManager.AppSettings["threshold"]);
string connectionString =
ConfigurationManager.AppSettings["connectionString"];
string sql =
@"select * from things
size > ";
sql += threshold;
using (SqlConnection connection =
new SqlConnection(connectionString)) {
connection.Open();
SqlCommand command = new SqlCommand(sql, connection);
using (SqlDataReader reader = command.ExecuteReader()) {
while (reader.Read()) {
string name = reader["Name"].ToString();
string destination = reader["destination"].ToString();
// do some business logic in here
doSomeBusinessLogic(name, destination, connection);
}
}
}
}
}
假设我们真正关心的是实际的业务处理,但我们的业务逻辑代码是与数据访问方面的焦点以及配置设置结合在一起的。那么,这种代码有可能出现什么错误呢?
第一个问题是该代码会因侧重点失偏而有些难以理解。我将在有关聚合的下一节中深入讨论这一点。
第二个问题是数据访问策略、数据库结构或配置策略中的任何更改同样也会波及整个业务逻辑代码,因为它们全部都属于同一个代码文件。这种业务逻辑对底层基础结构过于了解。
第三,我们不能独立于特定的数据库结构或在没有 AppSettings 键的情况下重用该业务逻辑代码。我们也不能重用在 BusinessLogicClass 中内嵌的数据访问功能。数据访问与业务逻辑之间的耦合可能不是问题,但如果我们希望改变此业务逻辑的用途,以对照由分析人员直接输入到 Excel 电子表格中的数据来使用它会怎样呢?如果我们要单独测试或调试该业务逻辑又会怎样呢?我们无法实现上述的任何操作,因为该业务逻辑与数据访问代码是紧密耦合的。如果我们能将业务逻辑从其他关注问题中隔离出来,则对它进行更改就会变得轻松得多。
总之,在类与模块之间实现松散耦合的实际目标是为了:
使代码更易于阅读。
将类的麻烦的内部运行隐藏在设计完善的 API 之后,从而使其他开发人员可以更轻松地使用这些类。
隔离对较小区域代码的可能更改。
在全新的上下文中重用类。
代码气味
在设计新代码时知道如何做该做的事情显然很好,但能够认识到您的现有代码或设计已出现问题则更为重要。 与不适当的亲密一样,“代码气味”(由 Martin Fowler 在著作《重构:改进既有代码的设计》中定义)是可用于发现代码中潜在问题的工具。
代码气味是表示代码中可能出错的一种信号。这并不意味着您需要立即移除您的现有代码并当场将其丢弃,但您确实需要仔细研究一下散发出恼人“气味”的代码。通过简单的检查即可发现代码气味。了解代码气味的作用很大,可帮助您在代码和类设计中的小问题演变为大问题之前就将其解决掉。
通常所描述的许多(如果不是极数)代码气味是拙劣的聚合或有负面影响的紧密耦合的标志。下面提供了一些其他示例:
发散式变化需要针对不同原因以不同方式进行更改的单个类。这种气味是表示该类没有聚合性的标志。您可以重构此类以将清楚的职责提取到新类中。
特性依恋ClassA 中的某方法似乎对 ClassB 的工作和数据字段过于感兴趣。从 ClassA 到 ClassB 的特性依恋是从 ClassA 到 ClassB 紧密耦合的体现。通常的解决办法是尝试将 ClassA 中感兴趣的方法的功能移至 ClassB,该功能已经与任务中涉及的大部分数据比较靠近。
霰弹式修改系统中某个特定类型的更改重复引起对一组类的多次小的更改。霰弹式修改通常意味着某单个逻辑概念或函数在多个类之间展开。可通过将代码中需要一起更改的所有部分集中到单个聚合类中来解决该问题。
提高聚合
聚合的理论定义是类的所有职责、数据和方法彼此关联紧密程度的度量标准。我更倾向于将聚合视为是判断某个类在系统内是否有明确定义的角色的度量标准。我们通常认为高度聚合是件好事,并且将“高度聚合”视为魔咒一样反复叨念。但其中的原因是什么?
让我们将编码看作是与计算机进行的一次对话。更准确地讲,我们在与计算机同时进行多个对话。我们的对话内容是有关如何实现安全性、基础结构方面的问题应如何表现以及业务规则是什么。
如果您处于一个正同时进行多个不同对话的喧闹聚会中,则很难将注意力集中在某一个您正设法进行的对话。在一个只进行一个对话的安静环境中进行对话就会容易得多。
对聚合的一个简单测试就是观察某个类并判断该类的所有内容是否与类的名称直接相关并由该名称描述,含糊的类名称(例如 InvoiceManager)不算在内。如果该类的职责不与其名称相关,则这些职责有可能属于其他类。如果您发现一些方法和字段的子集可轻松地在另一个类名称下单独分组,则您可能应将这些方法和字段提取到一个新类中。
举例说明,如果您在 TrainStation、Train 和 Conductor 类中发现一些方法和数据看起来与 TrainSchedule 类的主题最精确匹配,可将这些方法和数据移至 TrainSchedule 中。我最喜爱的有关设计的一句熟语在此处比较适用:将代码放置在您认为应该能找到它的位置。对我而言,将涉及列车时刻表的功能放置在 TrainSchedule 类中是最符合逻辑的。
延续我之前的对话比喻,由聚合类和子系统组成的系统就像是一个设计合理的在线讨论组。在线组中的每个区域都仅仅集中在一个特定主题,这样比较容易跟踪讨论内容,如果您正在寻找有关某特定主题的对话,则只能访问一个房间。
消除不适当的亲密
不适当的亲密指的是某个类中某个方法对另一个类过于熟知。不适当的亲密是两个类之间存在有负面影响的紧密耦合的迹象。假设我们有一个业务逻辑类,该类调用类 DataServer1 的实例以获取其进行业务逻辑处理所需的数据。图 2 显示了一个示例。在这种情况下,Process 方法需要了解 DataServer1 的大量内部工作,并对 SqlDataReader 类略知一二。
图 2 不适当的亲密
复制代码
public void Process() {
string connectionString = getConnectionString();
SqlConnection connection = new SqlConnection(connectionString);
DataServer1 server = new DataServer1(connection);
int daysOld = 5;
using (SqlDataReader reader = server.GetWorkItemData(daysOld)) {
while (reader.Read()) {
string name = reader.GetString(0);
string location = reader.GetString(1);
processItem(name, location);
}
}
}
现在让我们重新编写图 2 中的代码以消除不适当的亲密:
复制代码
public void Process() {
DataServer2 server = new DataServer2();
foreach (DataItem item in server.GetWorkItemData(5)) {
processItem(item);
}
}
正如您在此版本代码中看到的那样,我已将所有 SqlConnection 和 SqlDataReader 对象操作封装在 DataServer2 类内。DataServer2 也被假定为负责其自身的配置,因此新的 Process 方法无需了解任何有关设置 DataServer2 的内容。从 GetWorkItemData 返回的 DataItem 对象也是强类型化的对象。
现在,我们对照松散耦合的某些目标分析一下两个版本的 Process 方法。首先,在使代码易于阅读的方面效果如何?第一个版本和第二个版本的 Process 都执行了相同的基本任务,但哪一个更易于阅读和理解呢?就个人而言,我无需费力地理解数据访问代码就可以更轻松地阅读和理解业务逻辑处理。
在使类易于使用的方面效果如何?DataServer1 的使用者需要了解如何创建 SqlConnection 对象、了解返回的 DataReader 的结构,以及迭代并整理 DataReader。DataServer2 的使用者只需调用无参数的构造函数,然后调用可返回一系列强类型化的对象的单个方法即可。DataServer2 将负责自身的 ADO.NET 连接设置并整理打开的 DataReaders。
在隔离对较小区域代码的可能更改的方面效果如何?在第一个版本的代码中,对 DataServer 工作方式的几乎所有更改都会影响 Process 方法。在封装性更好的第二个版本的 DataServer 中,您可将数据存储切换到 Oracle 数据库或 XML 文件,而不会对 Process 方法产生任何影响。
Demeter 定律
Demeter 定律是一种设计经验法则。该定律的简要定义为:仅与您的直接伙伴交谈。Demeter 定律是有关代码潜在威胁的警告,如图 3 所示。
图 3 违背定律
复制代码
public interface DataService {
InsuranceClaim[] FindClaims(Customer customer);
}
public class Repository {
public DataService InnerService { get; set; }
}
public class ClassThatNeedsInsuranceClaim {
private Repository _repository;
public ClassThatNeedsInsuranceClaim(Repository repository) {
_repository = repository;
}
public void TallyAllTheOutstandingClaims(Customer customer) {
// This line of code violates the Law of Demeter
InsuranceClaim[] claims =
_repository.InnerService.FindClaims(customer);
}
}
ClassThatNeedsInsuranceClaim 类需要获取 InsuranceClaim 数据。它有一个对 Repository 类的引用,Repository 类本身包含一个 DataService 对象。ClassThatNeedsInsuranceClaim 到达 Repository 内部以获取内部 DataService 对象,然后调用 Repository.FindClaims 获得其数据。请注意,对 _repository.InnerService.FindClaims(customer) 的调用明显违背了 Demeter 定律,因为 ClassThatNeedsInsuranceClaim 将直接调用其 Repository 字段的属性的方法。现在,请将您注意力转向图 4,该图显示了同一代码的另一个示例,但这次它遵循了 Demeter 定律。
图 4 较好的解耦
复制代码
public class Repository2 {
private DataService _service;
public Repository2(DataService service) {
_service = service;
}
public InsuranceClaim[] FindClaims(Customer customer) {
// we're simply going to delegate to the inner
// DataService for now, but who knows what
// we want to do in the future?
return _service.FindClaims(customer);
}
}
public class ClassThatNeedsInsuranceClaim2 {
private Repository2 _repository;
public ClassThatNeedsInsuranceClaim2(
Repository2 repository) {
_repository = repository;
}
public void TallyAllTheOutstandingClaims(
Customer customer) {
// This line of code now follows the Law of Demeter
InsuranceClaim[] claims = _repository.FindClaims(customer);
}
}
我们实现了什么?Repository2 比 Repository 更易于使用,因为您有一个直接的方法来调用 InsuranceClaim 信息。在我们违反 Demeter 定律时,Repository 的使用者与 Repository 的实现紧密耦合。在修订过的代码中,我可以更好地更改 Repository 实现以添加更多高速缓存或以完全不同的对象换出基础 DataService。
Demeter 定律是一个功能强大的工具,可帮助您发现潜在的耦合问题,但不可盲目地遵循 Demeter 定律。违背 Demeter 定律的确会使您的系统实现更紧密的耦合,但有时候您可能会认为耦合到代码的某个稳定元素的潜在成本要比编写大量委托代码来避免违背 Demeter 定律的成本低。
只是告知,不要询问
“只是告知,不要询问”设计原则主张您告知对象将要执行什么任务。您不想做的事情是询问某对象其内部状态、对该状态做出决策,然后告知该对象将要执行什么任务。遵守“只是告知,不要询问”的对象交互风格是确保正确安置职责的有效途径。
图 5 说明了违背“只是告知,不要询问”原则的情况。该代码的任务是购买某种商品、确认 $10,000 以上的购买金额是否可能有折扣,最后检查帐户数据来判断是否有充足的资金。先前的 DumbPurchase 和 DumbAccount 类都无此功能。帐户和购买业务规则都是在 ClassThatUsesDumbEntities 中编码的。
图 5 过度询问
复制代码
public class DumbPurchase {
public double SubTotal { get; set; }
public double Discount { get; set; }
public double Total { get; set; }
}
public class DumbAccount {
public double Balance { get; set;}
}
public class ClassThatUsesDumbEntities {
public void MakePurchase(
DumbPurchase purchase, DumbAccount account) {
purchase.Discount = purchase.SubTotal > 10000 ? .10 : 0;
purchase.Total =
purchase.SubTotal*(1 - purchase.Discount);
if (purchase.Total < account.Balance) {
account.Balance -= purchase.Total;
}
else {
rejectPurchase(purchase,
"You don't have enough money.");
}
}
}
这种类型的代码在几个方面可能存在问题。在与此类似的系统中,可能会出现重复,因为某个实体的业务规则分散在这些实体之外的程序代码中。您可能会不知不觉地重复逻辑,因为先前编写的业务逻辑所在位置并不明显。
图 6 显示了同一代码,但这次遵循了“只是告知,不要询问”的模式。在此代码中,我将用于购买和帐户的业务规则移动到它们自己的 Purchase 和 Account 类中。当我们打算进行购买时,只需告诉 Account 类从自身扣除购买金额即可。Account 和 Purchase 了解它们自身及其内部规则。Account 的使用者只需要知道去调用 Account.Deduct(Purchase, PurchaseMessenger) 方法就可以了。
图 6 告知您的应用程序要执行什么任务
复制代码
public class Purchase {
private readonly double _subTotal;
public Purchase(double subTotal) {
_subTotal = subTotal;
}
public double Total {
get {
double discount = _subTotal > 10000 ? .10 : 0;
return _subTotal*(1 - discount);
}
}
}
public class Account {
private double _balance;
public void Deduct(
Purchase purchase, PurchaseMessenger messenger) {
if (purchase.Total < _balance) {
_balance -= purchase.Total;
}
else {
messenger.RejectPurchase(purchase, this);
}
}
}
public class ClassThatObeysTellDontAsk {
public void MakePurchase(
Purchase purchase, Account account) {
PurchaseMessenger messenger = new PurchaseMessenger();
account.Deduct(purchase, messenger);
}
}
Account 和 Purchase 对象比较易于使用,因为您无需对这些类有太多了解即可执行我们的业务逻辑。我们也潜在地减少了系统中的重复。我们不费吹灰之力就可以在整个系统中重用 Accounts 和 Purchases 的业务规则,因为这些规则位于 Account 和 Purchase 类的内部,而不是隐藏在使用这些类的代码内。另外,更改 Purchases 和 Accounts 的业务规则将更加轻松,因为这些规则只能在系统中的一个位置处找到。
与“只是告知,不要询问”紧密关联的是“信息专家”模式。如果您对您的系统有新的职责,那么新职责应属于哪个类呢?“信息专家”模式会问道,谁了解履行该职责所必需的信息?换言之,任何新职责的第一候选项都是已具有受该职责影响的数据字段的类。在购买示例中,Purchase 类知道您用于确定可能的折扣率的某个购买项的信息,因此 Purchase 类自身就是计算折扣率的直接候选项。
只说一次
作为一个行业,我们已了解到刻意编写可重用代码的成本是非常昂贵的,但我们仍因其明显的优点而设法实现重用。这对我们来说可能有必要在系统中查找重复项并找到消除或集中该重复项的方法。
在系统中提高聚合的最有效方法之一就是只要发现重复项就将其消除。如果您认为自己并不完全了解系统今后将要如何变化,但您可以通过在类结构中保持良好的聚合和耦合来改进代码接受更改的能力,这可能就是最佳的方法。
多年前我曾参与过一个大型装运应用程序的辅助设计工作,该应用程序用于管理某工厂车间的货箱流。在其最初的成形阶段,该系统轮询一个消息队列以获取外来消息,然后通过应用一大组业务规则以确定货箱的下一站目的地来响应这些消息。
次年,该业务需要从桌面客户端启动货箱路线逻辑。令人遗憾的是,业务逻辑代码与用于读取和写入 MQ Series 队列的机制间的耦合过于紧密。根据判断,将原始业务逻辑代码从 MQ Series 基础结构中解脱出来风险极大,因此在新的桌面客户端的并行库中重复了整个业务规则主体。该决定使得新的桌面客户端变得切实可行,但也使今后的所有工作更加困难,因为对货箱路线逻辑的每个更改都需要对两个截然不同的库进行并行更改,而这些种类的业务规则经常发生更改。
我们从这个实际案例中得到了几个教训。代码中的重复对于构建系统的组织会产生实际成本,而该重复很大程度上是由于类结构中耦合和聚合质量不佳导致的。这种情况会直接影响公司的盈亏状况。
找到在代码中检查重复的一种途径,就是增加一个在以后改进设计的机会。如果您发现两个或多个类有某些功能重复,您可以判定重复的功能一定是完全不同的职责。改进代码库的聚合质量的最佳方法之一是,将重复项提取到可在整个代码库中共享的单独类中。
但我得到的痛苦经验是,即使看似无负面影响的重复项也会让你头疼不已。随着 Microsoft .NET Framework 2.0 中泛型的出现,许多人都开始创建如下所示的参数化的 Repository 类:
复制代码
public interface IRepository<T> {
void Save(T subject);
void Delete(T subject);
}
在此接口中,T 是 Invoice、Order 或 Shipment 之类的域实体。StructureMap 的用户希望能够调用此代码并获得完全成形的能处理特定域实体的存储库对象,如 Invoice 对象:
复制代码
IRepository<Invoice> repository =
ObjectFactory.GetInstance<IRepository<Invoice>>();
这听起来是一个不错的功能,因此我着手为这些种类的参数化类型添加支持。对 StructureMap 进行这种更改后来证明是非常困难的,就是因为如下所示的代码:
复制代码
_PluginFamilies.Add(family.PluginType.FullName, family);
以及如下所示的代码:
复制代码
MementoSource source =
this.getMementoSourceForFamily(pluginType.FullName);
还有如下所示的代码:
复制代码
private IInstanceFactory this[Type PluginType] {
get {
return this[PluginType.FullName];
}
set {
this[PluginType.FullName] = value;
}
}
您是否已发现了重复?不要过于沉浸在此示例中,我有个毫无疑问的规则表明与 System.Type 相关的对象是通过将 Type.FullName 属性用作 Hashtable 中的键进行存储的。这是个毫不起眼的逻辑,但我已在整个代码库中重复了多次。在实现泛型时,我判定如果按实际类型而不是 Type.FullName 在内部存储对象,这个逻辑会更有效。
这个在行为方面做出的看似微小的变动却花费了我数天的时间,而不是先前假定的数小时,因为我已将这少量数据重复了很多次。我从中得到的教训是,对于系统中的任何规则,无论表面看来多么微不足道,都应只表达一次。
总结
聚合和耦合应用于设计和体系结构的每个级别,但我多数时候是侧重于类和方法级别的细粒度细节。当然,您最好具有较大的体系结构决策权 – 技术选择、项目构造和物理部署都很重要,但这些决策通常都完全限制在多个选择中,而权衡得失利弊之后所做的选择通常能够得到广泛的理解。
我发现您在类和方法级别所做的成千上百个小的决策的累积效应对项目的成功具有着深远的影响,而您也在小的事情上也得到了更多的选择和替代方案。尽管通常在生活中不一定是这样,但在软件设计中请不要忽视这些小事情。
开发人员们共有的看法是,担心所有这些聚合和耦合问题不过是影响工作进度的象牙塔理论。我的感受是,如果您的代码的聚合和耦合质量良好,会随着时间的推移一直保持代码的工作效率。我强烈建议您将对聚合和耦合质量的认识内在化到无需有意识地思考这些质量的程度。此外,我能够推荐的用于改进您的设计技巧的最佳练习之一就是重新回顾以前的编码成果,尝试找到本来可以改进这些旧代码的方法,然后设法回忆过去的设计中使代码易于更改或难以调整的元素。
请将您想询问的问题和提出的意见发送至 mmpatt@microsoft.com。
Jeremy Miller 是 Microsoft C# 方面的 MVP,他编写了使用 .NET 进行依赖关系注入的开源 StructureMap (structuremap.sourceforge.net) 工具,并即将推出用于在 .NET 中进行超动力 FIT 测试的 StoryTeller (storyteller.tigris.org) 工具。请访问他在 CodeBetter 网站上的博客“The Shade Tree Developer”,网址为 codebetter.com/blogs/jeremy.miller。
Jeremy Miller
目录
降低耦合
提高聚合
消除不适当的亲密
Demeter 定律
只是告知,不要询问
只说一次
总结
很多软件设计一直都存在一个问题:这段代码应放置在哪里?我一直在寻找编排代码的最佳方法,以便能够更轻松地编写、理解代码,并在以后更方便地进行更改。如果我的代码构造很漂亮,我将可名扬四海,无限荣光。如果构造得很糟糕,那些追随我的开发人员会一直对我埋怨不停。
我特别想在我的代码结构方面实现三个具体目标:
让代码中需要一起更改的部分尽可能靠近在一起。
允许代码中不相关的部分独立更改(也称为“正交性”)。
最大程度减少代码中的重复部分。
要实现这三个目标,我需要一些工具来协助我了解应将新代码放置到什么位置,并借助其他一些工具帮助我识别出是否将代码放置到了错误位置。
大体上说,这些目标都与聚合和耦合这两个典型代码质量紧密相关。我通过达到更高程度的聚合和更松散的耦合实现这些目标。当然,我们首先需要了解这些质量的含义以及为何聚合和耦合是有用的概念。然后我想讨论一些我将称之为“设计矢量”的内容,它们可以帮助我们实现更佳的结构,并帮助我们认识到何时需要丢弃那些已经不知不觉在我们代码中形成的错误结构。
补充一点,我是名为 StructureMap 的开源控制反转 (IOC) 工具的主要开发人员。我将使用源自 StructureMap 的一些现实示例来说明这些矢量所针对的设计问题。换言之,千万不要再犯我已犯过的错误。
降低耦合
在软件设计的几乎所有讨论场合,你都会随时听到“松散耦合”或“紧密耦合”这样的术语。类或子系统之间的耦合是这些类或子系统之间互联程度的量度标准。紧密耦合表示相关的类必须了解彼此的内部细节、更改将波及整个系统,并且系统可能更难以了解。图 1 显示了一个精心设计的业务处理模块示例,该模块与除业务逻辑之外的其他各种关注问题紧密耦合。
图 1 紧密耦合的代码
复制代码
public class BusinessLogicClass {
public void DoSomething() {
// Go get some configuration
int threshold =
int.Parse(ConfigurationManager.AppSettings["threshold"]);
string connectionString =
ConfigurationManager.AppSettings["connectionString"];
string sql =
@"select * from things
size > ";
sql += threshold;
using (SqlConnection connection =
new SqlConnection(connectionString)) {
connection.Open();
SqlCommand command = new SqlCommand(sql, connection);
using (SqlDataReader reader = command.ExecuteReader()) {
while (reader.Read()) {
string name = reader["Name"].ToString();
string destination = reader["destination"].ToString();
// do some business logic in here
doSomeBusinessLogic(name, destination, connection);
}
}
}
}
}
假设我们真正关心的是实际的业务处理,但我们的业务逻辑代码是与数据访问方面的焦点以及配置设置结合在一起的。那么,这种代码有可能出现什么错误呢?
第一个问题是该代码会因侧重点失偏而有些难以理解。我将在有关聚合的下一节中深入讨论这一点。
第二个问题是数据访问策略、数据库结构或配置策略中的任何更改同样也会波及整个业务逻辑代码,因为它们全部都属于同一个代码文件。这种业务逻辑对底层基础结构过于了解。
第三,我们不能独立于特定的数据库结构或在没有 AppSettings 键的情况下重用该业务逻辑代码。我们也不能重用在 BusinessLogicClass 中内嵌的数据访问功能。数据访问与业务逻辑之间的耦合可能不是问题,但如果我们希望改变此业务逻辑的用途,以对照由分析人员直接输入到 Excel 电子表格中的数据来使用它会怎样呢?如果我们要单独测试或调试该业务逻辑又会怎样呢?我们无法实现上述的任何操作,因为该业务逻辑与数据访问代码是紧密耦合的。如果我们能将业务逻辑从其他关注问题中隔离出来,则对它进行更改就会变得轻松得多。
总之,在类与模块之间实现松散耦合的实际目标是为了:
使代码更易于阅读。
将类的麻烦的内部运行隐藏在设计完善的 API 之后,从而使其他开发人员可以更轻松地使用这些类。
隔离对较小区域代码的可能更改。
在全新的上下文中重用类。
代码气味
在设计新代码时知道如何做该做的事情显然很好,但能够认识到您的现有代码或设计已出现问题则更为重要。 与不适当的亲密一样,“代码气味”(由 Martin Fowler 在著作《重构:改进既有代码的设计》中定义)是可用于发现代码中潜在问题的工具。
代码气味是表示代码中可能出错的一种信号。这并不意味着您需要立即移除您的现有代码并当场将其丢弃,但您确实需要仔细研究一下散发出恼人“气味”的代码。通过简单的检查即可发现代码气味。了解代码气味的作用很大,可帮助您在代码和类设计中的小问题演变为大问题之前就将其解决掉。
通常所描述的许多(如果不是极数)代码气味是拙劣的聚合或有负面影响的紧密耦合的标志。下面提供了一些其他示例:
发散式变化需要针对不同原因以不同方式进行更改的单个类。这种气味是表示该类没有聚合性的标志。您可以重构此类以将清楚的职责提取到新类中。
特性依恋ClassA 中的某方法似乎对 ClassB 的工作和数据字段过于感兴趣。从 ClassA 到 ClassB 的特性依恋是从 ClassA 到 ClassB 紧密耦合的体现。通常的解决办法是尝试将 ClassA 中感兴趣的方法的功能移至 ClassB,该功能已经与任务中涉及的大部分数据比较靠近。
霰弹式修改系统中某个特定类型的更改重复引起对一组类的多次小的更改。霰弹式修改通常意味着某单个逻辑概念或函数在多个类之间展开。可通过将代码中需要一起更改的所有部分集中到单个聚合类中来解决该问题。
提高聚合
聚合的理论定义是类的所有职责、数据和方法彼此关联紧密程度的度量标准。我更倾向于将聚合视为是判断某个类在系统内是否有明确定义的角色的度量标准。我们通常认为高度聚合是件好事,并且将“高度聚合”视为魔咒一样反复叨念。但其中的原因是什么?
让我们将编码看作是与计算机进行的一次对话。更准确地讲,我们在与计算机同时进行多个对话。我们的对话内容是有关如何实现安全性、基础结构方面的问题应如何表现以及业务规则是什么。
如果您处于一个正同时进行多个不同对话的喧闹聚会中,则很难将注意力集中在某一个您正设法进行的对话。在一个只进行一个对话的安静环境中进行对话就会容易得多。
对聚合的一个简单测试就是观察某个类并判断该类的所有内容是否与类的名称直接相关并由该名称描述,含糊的类名称(例如 InvoiceManager)不算在内。如果该类的职责不与其名称相关,则这些职责有可能属于其他类。如果您发现一些方法和字段的子集可轻松地在另一个类名称下单独分组,则您可能应将这些方法和字段提取到一个新类中。
举例说明,如果您在 TrainStation、Train 和 Conductor 类中发现一些方法和数据看起来与 TrainSchedule 类的主题最精确匹配,可将这些方法和数据移至 TrainSchedule 中。我最喜爱的有关设计的一句熟语在此处比较适用:将代码放置在您认为应该能找到它的位置。对我而言,将涉及列车时刻表的功能放置在 TrainSchedule 类中是最符合逻辑的。
延续我之前的对话比喻,由聚合类和子系统组成的系统就像是一个设计合理的在线讨论组。在线组中的每个区域都仅仅集中在一个特定主题,这样比较容易跟踪讨论内容,如果您正在寻找有关某特定主题的对话,则只能访问一个房间。
消除不适当的亲密
不适当的亲密指的是某个类中某个方法对另一个类过于熟知。不适当的亲密是两个类之间存在有负面影响的紧密耦合的迹象。假设我们有一个业务逻辑类,该类调用类 DataServer1 的实例以获取其进行业务逻辑处理所需的数据。图 2 显示了一个示例。在这种情况下,Process 方法需要了解 DataServer1 的大量内部工作,并对 SqlDataReader 类略知一二。
图 2 不适当的亲密
复制代码
public void Process() {
string connectionString = getConnectionString();
SqlConnection connection = new SqlConnection(connectionString);
DataServer1 server = new DataServer1(connection);
int daysOld = 5;
using (SqlDataReader reader = server.GetWorkItemData(daysOld)) {
while (reader.Read()) {
string name = reader.GetString(0);
string location = reader.GetString(1);
processItem(name, location);
}
}
}
现在让我们重新编写图 2 中的代码以消除不适当的亲密:
复制代码
public void Process() {
DataServer2 server = new DataServer2();
foreach (DataItem item in server.GetWorkItemData(5)) {
processItem(item);
}
}
正如您在此版本代码中看到的那样,我已将所有 SqlConnection 和 SqlDataReader 对象操作封装在 DataServer2 类内。DataServer2 也被假定为负责其自身的配置,因此新的 Process 方法无需了解任何有关设置 DataServer2 的内容。从 GetWorkItemData 返回的 DataItem 对象也是强类型化的对象。
现在,我们对照松散耦合的某些目标分析一下两个版本的 Process 方法。首先,在使代码易于阅读的方面效果如何?第一个版本和第二个版本的 Process 都执行了相同的基本任务,但哪一个更易于阅读和理解呢?就个人而言,我无需费力地理解数据访问代码就可以更轻松地阅读和理解业务逻辑处理。
在使类易于使用的方面效果如何?DataServer1 的使用者需要了解如何创建 SqlConnection 对象、了解返回的 DataReader 的结构,以及迭代并整理 DataReader。DataServer2 的使用者只需调用无参数的构造函数,然后调用可返回一系列强类型化的对象的单个方法即可。DataServer2 将负责自身的 ADO.NET 连接设置并整理打开的 DataReaders。
在隔离对较小区域代码的可能更改的方面效果如何?在第一个版本的代码中,对 DataServer 工作方式的几乎所有更改都会影响 Process 方法。在封装性更好的第二个版本的 DataServer 中,您可将数据存储切换到 Oracle 数据库或 XML 文件,而不会对 Process 方法产生任何影响。
Demeter 定律
Demeter 定律是一种设计经验法则。该定律的简要定义为:仅与您的直接伙伴交谈。Demeter 定律是有关代码潜在威胁的警告,如图 3 所示。
图 3 违背定律
复制代码
public interface DataService {
InsuranceClaim[] FindClaims(Customer customer);
}
public class Repository {
public DataService InnerService { get; set; }
}
public class ClassThatNeedsInsuranceClaim {
private Repository _repository;
public ClassThatNeedsInsuranceClaim(Repository repository) {
_repository = repository;
}
public void TallyAllTheOutstandingClaims(Customer customer) {
// This line of code violates the Law of Demeter
InsuranceClaim[] claims =
_repository.InnerService.FindClaims(customer);
}
}
ClassThatNeedsInsuranceClaim 类需要获取 InsuranceClaim 数据。它有一个对 Repository 类的引用,Repository 类本身包含一个 DataService 对象。ClassThatNeedsInsuranceClaim 到达 Repository 内部以获取内部 DataService 对象,然后调用 Repository.FindClaims 获得其数据。请注意,对 _repository.InnerService.FindClaims(customer) 的调用明显违背了 Demeter 定律,因为 ClassThatNeedsInsuranceClaim 将直接调用其 Repository 字段的属性的方法。现在,请将您注意力转向图 4,该图显示了同一代码的另一个示例,但这次它遵循了 Demeter 定律。
图 4 较好的解耦
复制代码
public class Repository2 {
private DataService _service;
public Repository2(DataService service) {
_service = service;
}
public InsuranceClaim[] FindClaims(Customer customer) {
// we're simply going to delegate to the inner
// DataService for now, but who knows what
// we want to do in the future?
return _service.FindClaims(customer);
}
}
public class ClassThatNeedsInsuranceClaim2 {
private Repository2 _repository;
public ClassThatNeedsInsuranceClaim2(
Repository2 repository) {
_repository = repository;
}
public void TallyAllTheOutstandingClaims(
Customer customer) {
// This line of code now follows the Law of Demeter
InsuranceClaim[] claims = _repository.FindClaims(customer);
}
}
我们实现了什么?Repository2 比 Repository 更易于使用,因为您有一个直接的方法来调用 InsuranceClaim 信息。在我们违反 Demeter 定律时,Repository 的使用者与 Repository 的实现紧密耦合。在修订过的代码中,我可以更好地更改 Repository 实现以添加更多高速缓存或以完全不同的对象换出基础 DataService。
Demeter 定律是一个功能强大的工具,可帮助您发现潜在的耦合问题,但不可盲目地遵循 Demeter 定律。违背 Demeter 定律的确会使您的系统实现更紧密的耦合,但有时候您可能会认为耦合到代码的某个稳定元素的潜在成本要比编写大量委托代码来避免违背 Demeter 定律的成本低。
只是告知,不要询问
“只是告知,不要询问”设计原则主张您告知对象将要执行什么任务。您不想做的事情是询问某对象其内部状态、对该状态做出决策,然后告知该对象将要执行什么任务。遵守“只是告知,不要询问”的对象交互风格是确保正确安置职责的有效途径。
图 5 说明了违背“只是告知,不要询问”原则的情况。该代码的任务是购买某种商品、确认 $10,000 以上的购买金额是否可能有折扣,最后检查帐户数据来判断是否有充足的资金。先前的 DumbPurchase 和 DumbAccount 类都无此功能。帐户和购买业务规则都是在 ClassThatUsesDumbEntities 中编码的。
图 5 过度询问
复制代码
public class DumbPurchase {
public double SubTotal { get; set; }
public double Discount { get; set; }
public double Total { get; set; }
}
public class DumbAccount {
public double Balance { get; set;}
}
public class ClassThatUsesDumbEntities {
public void MakePurchase(
DumbPurchase purchase, DumbAccount account) {
purchase.Discount = purchase.SubTotal > 10000 ? .10 : 0;
purchase.Total =
purchase.SubTotal*(1 - purchase.Discount);
if (purchase.Total < account.Balance) {
account.Balance -= purchase.Total;
}
else {
rejectPurchase(purchase,
"You don't have enough money.");
}
}
}
这种类型的代码在几个方面可能存在问题。在与此类似的系统中,可能会出现重复,因为某个实体的业务规则分散在这些实体之外的程序代码中。您可能会不知不觉地重复逻辑,因为先前编写的业务逻辑所在位置并不明显。
图 6 显示了同一代码,但这次遵循了“只是告知,不要询问”的模式。在此代码中,我将用于购买和帐户的业务规则移动到它们自己的 Purchase 和 Account 类中。当我们打算进行购买时,只需告诉 Account 类从自身扣除购买金额即可。Account 和 Purchase 了解它们自身及其内部规则。Account 的使用者只需要知道去调用 Account.Deduct(Purchase, PurchaseMessenger) 方法就可以了。
图 6 告知您的应用程序要执行什么任务
复制代码
public class Purchase {
private readonly double _subTotal;
public Purchase(double subTotal) {
_subTotal = subTotal;
}
public double Total {
get {
double discount = _subTotal > 10000 ? .10 : 0;
return _subTotal*(1 - discount);
}
}
}
public class Account {
private double _balance;
public void Deduct(
Purchase purchase, PurchaseMessenger messenger) {
if (purchase.Total < _balance) {
_balance -= purchase.Total;
}
else {
messenger.RejectPurchase(purchase, this);
}
}
}
public class ClassThatObeysTellDontAsk {
public void MakePurchase(
Purchase purchase, Account account) {
PurchaseMessenger messenger = new PurchaseMessenger();
account.Deduct(purchase, messenger);
}
}
Account 和 Purchase 对象比较易于使用,因为您无需对这些类有太多了解即可执行我们的业务逻辑。我们也潜在地减少了系统中的重复。我们不费吹灰之力就可以在整个系统中重用 Accounts 和 Purchases 的业务规则,因为这些规则位于 Account 和 Purchase 类的内部,而不是隐藏在使用这些类的代码内。另外,更改 Purchases 和 Accounts 的业务规则将更加轻松,因为这些规则只能在系统中的一个位置处找到。
与“只是告知,不要询问”紧密关联的是“信息专家”模式。如果您对您的系统有新的职责,那么新职责应属于哪个类呢?“信息专家”模式会问道,谁了解履行该职责所必需的信息?换言之,任何新职责的第一候选项都是已具有受该职责影响的数据字段的类。在购买示例中,Purchase 类知道您用于确定可能的折扣率的某个购买项的信息,因此 Purchase 类自身就是计算折扣率的直接候选项。
只说一次
作为一个行业,我们已了解到刻意编写可重用代码的成本是非常昂贵的,但我们仍因其明显的优点而设法实现重用。这对我们来说可能有必要在系统中查找重复项并找到消除或集中该重复项的方法。
在系统中提高聚合的最有效方法之一就是只要发现重复项就将其消除。如果您认为自己并不完全了解系统今后将要如何变化,但您可以通过在类结构中保持良好的聚合和耦合来改进代码接受更改的能力,这可能就是最佳的方法。
多年前我曾参与过一个大型装运应用程序的辅助设计工作,该应用程序用于管理某工厂车间的货箱流。在其最初的成形阶段,该系统轮询一个消息队列以获取外来消息,然后通过应用一大组业务规则以确定货箱的下一站目的地来响应这些消息。
次年,该业务需要从桌面客户端启动货箱路线逻辑。令人遗憾的是,业务逻辑代码与用于读取和写入 MQ Series 队列的机制间的耦合过于紧密。根据判断,将原始业务逻辑代码从 MQ Series 基础结构中解脱出来风险极大,因此在新的桌面客户端的并行库中重复了整个业务规则主体。该决定使得新的桌面客户端变得切实可行,但也使今后的所有工作更加困难,因为对货箱路线逻辑的每个更改都需要对两个截然不同的库进行并行更改,而这些种类的业务规则经常发生更改。
我们从这个实际案例中得到了几个教训。代码中的重复对于构建系统的组织会产生实际成本,而该重复很大程度上是由于类结构中耦合和聚合质量不佳导致的。这种情况会直接影响公司的盈亏状况。
找到在代码中检查重复的一种途径,就是增加一个在以后改进设计的机会。如果您发现两个或多个类有某些功能重复,您可以判定重复的功能一定是完全不同的职责。改进代码库的聚合质量的最佳方法之一是,将重复项提取到可在整个代码库中共享的单独类中。
但我得到的痛苦经验是,即使看似无负面影响的重复项也会让你头疼不已。随着 Microsoft .NET Framework 2.0 中泛型的出现,许多人都开始创建如下所示的参数化的 Repository 类:
复制代码
public interface IRepository<T> {
void Save(T subject);
void Delete(T subject);
}
在此接口中,T 是 Invoice、Order 或 Shipment 之类的域实体。StructureMap 的用户希望能够调用此代码并获得完全成形的能处理特定域实体的存储库对象,如 Invoice 对象:
复制代码
IRepository<Invoice> repository =
ObjectFactory.GetInstance<IRepository<Invoice>>();
这听起来是一个不错的功能,因此我着手为这些种类的参数化类型添加支持。对 StructureMap 进行这种更改后来证明是非常困难的,就是因为如下所示的代码:
复制代码
_PluginFamilies.Add(family.PluginType.FullName, family);
以及如下所示的代码:
复制代码
MementoSource source =
this.getMementoSourceForFamily(pluginType.FullName);
还有如下所示的代码:
复制代码
private IInstanceFactory this[Type PluginType] {
get {
return this[PluginType.FullName];
}
set {
this[PluginType.FullName] = value;
}
}
您是否已发现了重复?不要过于沉浸在此示例中,我有个毫无疑问的规则表明与 System.Type 相关的对象是通过将 Type.FullName 属性用作 Hashtable 中的键进行存储的。这是个毫不起眼的逻辑,但我已在整个代码库中重复了多次。在实现泛型时,我判定如果按实际类型而不是 Type.FullName 在内部存储对象,这个逻辑会更有效。
这个在行为方面做出的看似微小的变动却花费了我数天的时间,而不是先前假定的数小时,因为我已将这少量数据重复了很多次。我从中得到的教训是,对于系统中的任何规则,无论表面看来多么微不足道,都应只表达一次。
总结
聚合和耦合应用于设计和体系结构的每个级别,但我多数时候是侧重于类和方法级别的细粒度细节。当然,您最好具有较大的体系结构决策权 – 技术选择、项目构造和物理部署都很重要,但这些决策通常都完全限制在多个选择中,而权衡得失利弊之后所做的选择通常能够得到广泛的理解。
我发现您在类和方法级别所做的成千上百个小的决策的累积效应对项目的成功具有着深远的影响,而您也在小的事情上也得到了更多的选择和替代方案。尽管通常在生活中不一定是这样,但在软件设计中请不要忽视这些小事情。
开发人员们共有的看法是,担心所有这些聚合和耦合问题不过是影响工作进度的象牙塔理论。我的感受是,如果您的代码的聚合和耦合质量良好,会随着时间的推移一直保持代码的工作效率。我强烈建议您将对聚合和耦合质量的认识内在化到无需有意识地思考这些质量的程度。此外,我能够推荐的用于改进您的设计技巧的最佳练习之一就是重新回顾以前的编码成果,尝试找到本来可以改进这些旧代码的方法,然后设法回忆过去的设计中使代码易于更改或难以调整的元素。
请将您想询问的问题和提出的意见发送至 mmpatt@microsoft.com。
Jeremy Miller 是 Microsoft C# 方面的 MVP,他编写了使用 .NET 进行依赖关系注入的开源 StructureMap (structuremap.sourceforge.net) 工具,并即将推出用于在 .NET 中进行超动力 FIT 测试的 StoryTeller (storyteller.tigris.org) 工具。请访问他在 CodeBetter 网站上的博客“The Shade Tree Developer”,网址为 codebetter.com/blogs/jeremy.miller。
相关推荐
博客原址http://blog.csdn.net/tumiz/article/details/22224067 转载自http://www.nowamagic.net/program/program_PolymerizationAndCoupling.php
此外,模块的独立性可以通过聚合和耦合两个标准来衡量,理想的模块应该是高聚合、低耦合,这有助于降低接口成本,提高软件的可维护性和扩展性。 抽象和逐步求精是程序设计中的思维方式,抽象帮助我们理解复杂问题的...
- 模块的独立性通常用聚合和耦合来衡量。聚合度量模块内部元素的紧密程度,理想情况下,模块应有高聚合度。耦合度量模块间的依赖程度,低耦合度代表模块间相互影响较小,更利于维护和重用。 4. **IT 战略规划**: ...
3. **模块独立性**:衡量模块的两个主要标准是聚合和耦合。聚合度量模块内部元素的紧密程度,理想情况下,模块应有高内聚,表示内部元素紧密相关。耦合度量模块间的相互依赖,低耦合更佳,意味着模块间相互独立。 4...
在聚合收缩与吸湿膨胀的耦合作用下,充填材料的刚度降低会导致界面应力增加和受损牙齿位移增大。长期作用和缓慢发展的情况下,这种损伤作用对牙齿充填材料的使用寿命产生不利影响。 文章还探讨了不同的吸水性(超...
以太网交换机端口耦合,也称为端口聚合或Link Aggregation,是一种网络技术,用于通过将多个物理端口组合成一个逻辑接口来提高带宽和提供链路冗余。这种技术常用于数据中心、企业网络和高性能计算环境中,以增加...
山本耦合反应通常在温和的条件下进行,需要在干燥、惰性气体环境中进行反应,以避免水分和氧气对催化剂的影响。反应溶剂可以是多种溶剂,如 DMF、toluene 等。 催化剂: 山本耦合反应的催化剂是镍(II)络合物...
应用耦合模理论、电光调制理论、保角变换法和镜像法, 给出了分析聚合物定向耦合电光开关高频响应特性的功率传输矩阵新方法, 导出了输出功率、上升时间、下降时间、开关时间及截止开关频率的表达式。为了获得较低的...
例如,当光从一个介质进入另一个介质时,由于折射率的不连续性会产生反射和折射,这将对耦合波的传播特性产生影响。 全息理论部分,文章介绍了全息的基本概念,即通过两束或多束相干光的干涉记录下物体的全部信息,...
4. **理顺对象图**:分析并调整对象之间的关系,消除不必要的复杂性和耦合。 5. **调整聚合边界**:根据业务需求和模型的演变,适时调整聚合的边界,使其保持清晰和有效。 案例分析:项目管理上下文 在项目管理领域...
Spring 是一个全面的企业级应用开发框架,它提供了依赖注入(Dependency Injection,DI)和面向切面编程(Aspect-Oriented Programming,AOP)等核心特性,有助于减少代码间的耦合。在SSM框架中,Spring作为整体架构...
DDD是一种将业务领域的复杂性转化为软件模型的设计策略,强调通过与领域专家紧密合作,理解和表达业务核心概念,以创建高度内聚、低耦合的系统。在工作坊中,张逸可能涵盖了以下主要知识点: 1. **领域建模**:DDD...
合成聚合复用原则,简称CARP或CRP,是面向对象设计中的一项重要原则,它强调在构建新对象时应优先考虑使用已有对象的合成和聚合,而不是通过继承来达到复用目的。这一原则旨在降低类之间的耦合度,提高系统的可维护...
通过上述知识点的总结,我们可以得出侧壁光栅辅助聚合物波导定向耦合器是一种高效的光学器件,它能够实现多模与单模波导间基本模式的高效率耦合,并通过简单的制备工艺和精确的温度调谐实现高效的模式选择和传输。...
而事件聚合器是一种设计模式,用于集中管理和分发应用中的事件,它有助于解耦事件发布者和订阅者,使代码更加灵活和可维护。 在传统的WPF事件处理中,一个控件直接将事件绑定到另一个控件或业务逻辑层。例如,按钮...
光在聚合物光纤(POF)中传输时存在较强的模式耦合,由于模式耦合的影响,聚合物光纤的传输带宽得以提高。根据能流方程对阶跃型聚合物光纤光传输中的模式耦合进行了研究,从实验上测量了聚合物光纤的模式耦合系数,测量...
设计并制备了一种基于非对称定向耦合结构的聚合物波导热光模式开关。该器件由一个单模波导和一个双模波导组成。在双模波导上制作金属加热器,通过向加热器提供驱动电压,可以实现单模波导中的LP01模与双模波导中的LP...
### 具有两段余弦传递CPWG电极的聚合物定向耦合器光电开关的傅立叶分析:新的理论观点 #### 摘要与背景介绍 本研究旨在通过采用特殊的电极结构来优化聚合物定向耦合器(DC)电光(EO)开关的设计。这种开关利用两...
仿真结果验证了所提算法的有效性,显示在非光滑聚合博弈中,智能体能够通过分布式连续时间更新策略,成功地收敛到纳什均衡,即使面临复杂的非光滑性和耦合不等式约束。 总的来说,这项工作为解决多智能体系统中的非...