首页 / 资讯中心 / 文章详情

【设计模式之美】面向对象分析方法论与实现(二):需求到接口实现的方法论

【设计模式之美】面向对象分析方法论与实现(二):需求到接口实现的方法论 ★ FEATURED ARTICLE
文章目录一. 进行面向对象设计1. 划分职责需要有哪些类2. 定义类及其属性和方法3. 定义类与类之间的交互关系4. 将类组装起来并提供执行入口二. 如何进行面向对象编程1. 接口实现2. 辩证思考与灵活应用【设计模式之美】面向对象分析方法论与实现一需求分析方法论 描述了如何进行需求描述本文描述根据示例需求如何来进行面向对象设计OOD和面向对象编程OOP。描述怎么根据需求描述确定类类封装了单个需求的逻辑代表了能够处理这个需求的所有逻辑。定义类与类之间的关系拼装类实现鉴权逻辑一. 进行面向对象设计面向对象分析的产出是详细的需求描述那面向对象设计的产出就是类。在面向对象设计环节我们将需求描述转化为具体的类的设计。我们把这一设计环节拆解细化一下主要包含以下几个部分1. 划分职责需要有哪些类根据需求描述把其中涉及的功能点一个一个罗列出来然后再去看哪些功能点职责相近操作同样的属性是否应该归为同一个类。需求描述重放拆解需求拆解出来的每个功能点要尽可能的小。每个功能点只负责做一件很小的事情要符合“单一职责”。拆解需求后得到的功能点列表1、2、6、7 都是跟 token 有关负责 token 的生成、验证3、4 都是在处理 URL负责 URL 的拼接、解析5 是操作 AppID 和密码负责从存储中读取 AppID 和密码。可以粗略地得到三个核心的类AuthToken、Url、CredentialStorage。AuthToken 负责实现 1、2、6、7 这四个操作Url 负责 3、4 两个操作CredentialStorage 负责 5 这个操作。当然这是一个初步的类的划分其他一些不重要的、边边角角的类我们可能暂时没法一下子想全但这也没关系面向对象分析、设计、编程本来就是一个循环迭代、不断优化的过程。2. 定义类及其属性和方法通过分析需求描述识别出了三个核心的类它们分别是 AuthToken、Url 和 CredentialStorage。现在我们来看下每个类都有哪些属性和方法。我们还是从功能点列表中挖掘。AuthToken我们可以发现这样三个小细节。并不是所有出现的名词都被定义为类的属性比如 URL、AppID、密码、时间戳这几个名词我们把它作为了方法的参数。需要挖掘一些没有出现在功能点描述中属性比如 createTimeexpireTimeInterval它们用在 isExpired() 函数中用来判定 token 是否过期。我们还给 AuthToken 类添加了一个功能点描述中没有提到的方法 getToken()。单一职责第一个细节告诉我们从业务模型上来说不应该属于这个类的属性和方法不应该被放到这个类里。比如 URL、AppID 这些信息从业务模型上来说不应该属于 AuthToken所以我们不应该放到这个类中。从业务模型来梳理属性和方法第二、第三个细节告诉我们在设计类具有哪些属性和方法的时候不能单纯地依赖当下的需求还要分析这个类从业务模型上来讲理应具有哪些属性和方法。这样可以一方面保证类定义的完整性另一方面不仅为当下的需求还为未来的需求做些准备。Url 类注意接口请求并不一定是以 URL 的形式来表达还有可能是 Dubbo、RPC 等其他形式。为了让这个类更加通用命名更加贴切我们接下来把它命名为 ApiRequest。CredentialStorage 类3. 定义类与类之间的交互关系类与类之间都有哪些交互关系呢UML 统一建模语言中定义了六种类之间的关系。它们分别是泛化、实现、关联、聚合、组合、依赖。关系比较多而且有些还比较相近比如聚合和组合。泛化Generalization可以简单理解为继承关系。publicclassA{...}publicclassBextendsA{...}实现Realization一般是指接口和实现类之间的关系。publicinterfaceA{...}publicclassBimplementsA{...}聚合Aggregation是一种包含关系局部变量的感觉。A 类对象包含 B 类对象B 类对象的生命周期不依赖 A 类对象的生命周期比如课程与学生之间的关系。publicclassA{privateBb;publicA(Bb){this.bb;}}组合Composition也是一种包含关系成员变量的感觉。A 类对象包含 B 类对象B 类对象的生命周期依赖 A 类对象的生命周期B 类对象不可单独存在比如鸟与翅膀之间的关系。publicclassA{privateBb;publicA(){this.bnewB();}}关联Association是一种非常弱的关系包含聚合、组合两种关系。具体到代码层面如果 B 类对象是 A 类的成员变量那 B 类和 A 类就是关联关系。publicclassA{privateBb;publicA(Bb){this.bb;}}或者publicclassA{privateBb;publicA(){this.bnewB();}}依赖Dependency是一种比关联关系更加弱的关系包含关联关系。不管是 B 类对象是 A 类对象的成员变量还是 A 类的方法使用 B 类对象作为参数或者返回值、局部变量只要 B 类对象和 A 类对象有任何使用关系我们都称它们有依赖关系。publicclassA{privateBb;publicA(Bb){this.bb;}}或者publicclassA{privateBb;publicA(){this.bnewB();}}或者publicclassA{publicvoidfunc(Bb){...}}我们这里只关注泛化、实现、组合、依赖。4. 将类组装起来并提供执行入口接下来我们要将所有的类组装在一起提供一个执行入口。这个入口可能是一个 main() 函数也可能是一组给外部用的 API 接口。通过这个入口我们能触发整个代码跑起来。接口鉴权并不是一个独立运行的系统而是一个集成在系统上运行的组件所以我们通过接口封装所有的实现细节设计了一个最顶层的 ApiAuthenticator 接口类暴露一组给外部调用者使用的 API 接口作为触发执行鉴权逻辑的入口。二. 如何进行面向对象编程1. 接口实现面向对象设计完成之后我们已经定义清晰了类、属性、方法、类之间的交互并且将所有的类组装起来提供了统一的执行入口。接下来面向对象编程的工作就是将这些设计思路翻译成代码实现。有了前面的类图这部分工作相对来说就比较简单了。所以这里只给出比较复杂的 ApiAuthenticator 的实现。publicinterfaceApiAuthenticator{voidauth(Stringurl);voidauth(ApiRequestapiRequest);}publicclassDefaultApiAuthenticatorImplimplementsApiAuthenticator{privateCredentialStoragecredentialStorage;publicDefaultApiAuthenticatorImpl(){this.credentialStoragenewMysqlCredentialStorage();}publicDefaultApiAuthenticatorImpl(CredentialStoragecredentialStorage){this.credentialStoragecredentialStorage;}Overridepublicvoidauth(Stringurl){ApiRequestapiRequestApiRequest.buildFromUrl(url);auth(apiRequest);}Overridepublicvoidauth(ApiRequestapiRequest){StringappIdapiRequest.getAppId();StringtokenapiRequest.getToken();longtimestampapiRequest.getTimestamp();StringoriginalUrlapiRequest.getOriginalUrl();AuthTokenclientAuthTokennewAuthToken(token,timestamp);if(clientAuthToken.isExpired()){thrownewRuntimeException(Token is expired.);}StringpasswordcredentialStorage.getPasswordByAppId(appId);AuthTokenserverAuthTokenAuthToken.generate(originalUrl,appId,password,timestamp);if(!serverAuthToken.match(clientAuthToken)){thrownewRuntimeException(Token verfication failed.);}}}2. 辩证思考与灵活应用在平时的工作中大部分程序员往往都是在脑子里或者草纸上完成面向对象分析和设计然后就开始写代码了边写边思考边重构并不会严格地按照刚刚的流程来执行。而且说实话即便我们在写代码之前花很多时间做分析和设计绘制出完美的类图、UML 图也不可能把每个细节、交互都想得很清楚。在落实到代码的时候我们还是要反复迭代、重构、打破重写。参考《设计模式之美》
阅读完成 · 觉得有帮助?
咨询建站