12、面向对象设计到 Ada 的映射

面向对象设计到 Ada 的映射

1. 类关系与继承

在面向对象设计中,存在两种重要的关系:泛化 - 特化关系(也称为接口继承)和实现继承。泛化 - 特化关系源于系统对象模型,例如储蓄账户是账户的一种。而实现继承则是在审查类描述时,发现多个类的共同模式,将这些共同特征提取出来创建新的父类。

以下是一些关键类的定义:

-- 抽象类 Account
abstract class Account
    attribute Number: Account_Number;
    attribute Balance: Money;
    attribute constant Owner: unbound shared Customer;
    method Check Balance () : Money;
    method Deposit_Cash (Amount: Money);
    method Withdraw Cash (Amount: Money);
    method Transfer (Dest: Account; Amount: Money);
    method Owner Of () : Customer;
    local method Deposit (Amount: Money);
    local abstract method Withdraw (Amount: Money);
endclass

-- 类 Checking,继承自 Account
class Checking isa Account
    attribute Credit Limit: Money;
    local method Withdraw (Amount: Money);
    -- Raises Overrun Error if withdrawing would exceed the credit limit.
endclass

-- 类 Savings,继承自 Account
class Savings isa Account
    attribute Interest Rate: Rate;
    local method Withdraw (Amount: Money);
    -- Raises Overrun Error if withdrawing would result in an overdraft.
endclass

-- 类 Customer
class Customer
    attribute Name: Name_Type;
    attribute Address: Address_Type;
    attribute constant Owned_Checking: bound shared Checking;
    attribute constant Owned_Savings: bound shared Savings;
    attribute Transactions: col Transaction;
    method Checking_Account_Of () : Checking;
    method Savings_Account_Of () : Savings;
    method File (T: Transaction);
endclass

-- 类 Transaction
class Transaction
    attribute Date: Date_Type;
    attribute Amount: Money;
    attribute constant Source: shared Account;
    -- Source is undefined for a cash deposit.
    attribute constant Dest: shared Account;
    -- Dest is undefined for a cash withdrawal.
endclass

-- 类 Customer Services
class Customer Services
    attribute constant Customers: col Customer;
    method Generate_Monthly_Reports;
endclass

这些类之间的关系可以用以下表格总结:
| 类名 | 父类 | 关键属性 | 关键方法 |
| ---- | ---- | ---- | ---- |
| Account | 无 | Number, Balance, Owner | Check Balance, Deposit_Cash, Withdraw Cash, Transfer |
| Checking | Account | Credit Limit | Withdraw |
| Savings | Account | Interest Rate | Withdraw |
| Customer | 无 | Name, Address, Owned_Checking, Owned_Savings, Transactions | Checking_Account_Of, Savings_Account_Of, File |
| Transaction | 无 | Date, Amount, Source, Dest | 无 |
| Customer Services | 无 | Customers | Generate_Monthly_Reports |

2. 相互依赖关系

在设计阶段或开始实现时,需要关注类之间的相互依赖关系,并尽可能将它们解耦,主要有以下三个原因:
- 难以保证一致性 :类之间的关系最初在分析阶段的类模型中出现,之后会被“设计掉”,但对象属性和集合属性往往是这些关系的残留。独立的单向链接既不能确保一致性,也不能强制执行底层关系的基数约束。
- 难以维护 :包含许多相互依赖类的实现难以维护,因为要完全理解一个类,必须同时理解它所依赖的所有类,而且对一个类的更改可能会影响所有依赖类。
- Ada 语言限制 :在 Ada 中,包规范之间不允许存在相互依赖关系。因此,相互依赖的类必须在同一个包规范中声明,这对物理模块化造成了阻碍。

要找出类之间的相互依赖关系,只需查看可见性和继承图中的循环。例如,在银行交易案例中,图中包含三个循环,其中一个是 Account - Customer - Transaction - Account 。虽然诊断相互依赖关系很容易,但处理它们却更困难。

对于相互依赖关系,有以下几种处理方法:
- 检查设计 :有些相互依赖关系是设计不佳的标志,特别是对象交互图中的数据流语义。如果可能,应修改它们以消除消息传递中的循环。
- 判断是否巧合 :有些相互依赖关系可能是巧合的,例如房子的主人是一个人,而一个人住在房子里,但这两个人可能没有关系。在这种情况下,链接可以暴露出来,因为它们是独立的。
- 追溯到类模型关系 :尽可能将相互依赖关系追溯到类模型中的关系,隐藏单向链接,并提供操作来创建、一致更新所有链接并导航它们。
- 其他方法 :如果上述方法都不起作用,可以保留相互依赖关系,或者添加一个纯人工类来打破它。

以下是一个简单的 mermaid 流程图,展示处理相互依赖关系的流程:

graph TD;
    A[发现相互依赖关系] --> B{是否是设计问题};
    B -- 是 --> C[修改设计];
    B -- 否 --> D{是否是巧合};
    D -- 是 --> E[暴露链接];
    D -- 否 --> F[追溯到类模型关系];
    F --> G[隐藏单向链接并提供操作];
    C --> H{是否解决};
    E --> H;
    G --> H;
    H -- 否 --> I[保留或添加人工类];
    H -- 是 --> J[完成处理];

3. 类描述的映射

为了提供可能的未来“继承”,根类通常会在包中实现为标记类型。子类通过类型扩展实现,即从标记类型派生,通常包含在子包中。多个相关的类实现可能会包含在一个包中。

3.1 命名约定

一个类对应两个实现实体:一个包和一个类型。类的标识符不能同时命名包和类型,而且类的任何方法都会成为类型的原始操作,这意味着它有一个该类型的形式参数,也必须命名。我们将使用类名的复数形式作为包名,使用后缀作为类型名,变量、组件和参数可以直接使用“类”名。

3.2 抽象数据类型实现

通常,类应该实现为抽象数据类型(ADT),即标记类型或扩展部分是私有的。对于任何 ADT,必须为所有属性提供完整的构造函数、修改器和选择器操作。

3.3 属性映射

  • 值属性 :例如账户的余额,成为标记类型或其私有视图的组件。它们可以是预定义或程序员定义的数据类型,有时值属性可以通过方法实现。
  • 集合属性 :类似于值属性,集合本身通常是组件,而不是集合的访问值。
  • 对象属性 :通过持有对象引用的组件实现,使用引用是为了保留对象的身份。为了满足可能的需求,标记类型声明通常会附带一个对类范围类型的通用访问类型。

3.4 方法映射

方法通常会映射到实现类的标记类型的原始操作,该标记类型成为操作的显式参数。与 C++ 或 Java 相比,Ada 的这种特性允许通过参数的模式指定对该参数的访问控制。本地方法(仅在类及其子类中可见)应映射到私有操作,即在包的私有部分声明。

3.5 其他映射情况

  • 实现继承 :最好使用私有/隐藏继承,即父类型在包的私有部分派生。
  • Mixin 类 :如果一个类是 mixin,在 Ada 中最好的做法是结合使用泛型实例化和类型派生。
  • 常量对象属性 :有时可以映射到访问判别式,使用访问判别式的优点是组件始终是定义的,不会为空。
  • 绑定对象属性 :可以通过受控访问值实现,控制访问值用于控制引用的服务器对象本身,并使其生命周期与包含对象的生命周期同步。
  • 独占访问 :可以通过两种方式强制对服务器对象的独占访问:一是只有一条访问路径,且该路径隐藏在客户端对象的私有部分;二是使用受保护类型实现服务器类,但这样语言的内置继承功能将不可用,如有需要则必须使用一些变通方法。

以下是一个简单的代码示例,展示如何映射类描述:

-- 包 Accounts
package Accounts
is
    type Account_Type is abstract tagged ...
end Accounts;

-- 子包 Accounts.Savings
package Accounts.Savings
is
    type Savings_Account is new Account_Type with ...
end Accounts.Savings;

4. 集合映射

集合会根据所需的操作和性能要求映射到经典的数据结构,如集合、列表、队列、栈等。重要的是,对象集合应持有这些对象的引用,而不是对象本身,这是为了保留对象的身份。例如,与客户相关的交易是一个集合属性。

5. 方法体实现

方法体实现所需的大部分信息包含在对象交互图中,主要需要处理的问题是集合迭代和错误处理。异常用于错误处理,迭代可以通过以下几种常见的实现模式实现:
- 被动泛型迭代器
- 带有子程序访问参数的被动迭代器
- 主动迭代器

6. 银行交易案例映射

6.1 Person 类映射

package Persons is
    type Name_Type...; type Address_Type...;
    type Person_Type is abstract tagged limited
        -- could be non limited, and even non abstract
        record
            Name: Name_Type;
            Address: Address_Type;
        end record;
    type Person_Ref is access all Person_Type'Class;
end Persons;

6.2 Account 类映射

with Persons; use Persons;
package Accounts is
    type Account_Number_Type is ...;
    type Money_Type is ...; type Rate_Type is...;
    type Account_Type is abstract tagged limited private;
    procedure Create (Account: in out Account_Type; Owner: in Person_Ref);
    -- Can be called only once. A check is made that Owner is not null,
    -- and that s/he is in Customer'Class.
    -- The account then gets a unique account number.
    function Owner_Of (Account: Account_Type)return Person_Ref;
    function Account_Number (Account: Account_Type) return Account_Number_Type;
    function Check Balance (Account: Account_Type) return Money_Type;
    procedure Deposit_Cash (Account: in out Account_Type; Amount: in Money_Type);
    procedure Withdraw Cash (Account: in out Account_Type; Amount: in Money_Type);
    procedure Transfer (Source: in out Account_Type; Dest: in out Account_Type'Class; Amount: in Money_Type);
    -- Dest must not be limited to the specific type!
    Overrun Error: exception;
private
    procedure Deposit (Account: in out Account_Type; Amount: in Money_Type);
    procedure Withdraw (Account: in out Account_Type; Amount: in Money_Type) is abstract;
    -- Raises Overrun Error if withdrawing is impossible.
    type Account_Type is abstract tagged limited
        record
            Owner: Person Ref;
            Number : Account_Number_Type := 0;
            Balance: Money_Type := 0.0;
        end record;
end Accounts;

6.3 Checking 类映射

package Accounts.Checking is
    type Checking_Type is new Account_Type with private;
    type Checking_Ref is access all Checking_Type'Class;
    procedure Set Credit Limit (Account: in out Checking_Type; Amount: in Money_Type);
    function Credit Limit Of (Account: Checking_Type) return Money_Type;
    Overrun_Error: exception renames Accounts.Overrun_Error;
private
    procedure Withdraw (Account: in out Checking_Type; Amount: in Money_Type);
    -- Raises Overrun Error if withdrawing would exceed the credit limit.
    type Checking_Type is new Account_Type with record
        Credit_Limit: Money_Type := 0.0;
    end record;
end Accounts.Checking;

6.4 Transaction 类映射

with Accounts; use Accounts;
package Transactions is
    subtype Date_Type is Ada. Calendar.Time;
    type Transaction_Type(Source: in Account_Ref; Dest: in Account_Ref) is tagged limited private;
    type Transaction_Ref is access all Transaction_Type'Class;
    procedure Create (Transaction: out Transaction_Type; Amount: in Money_Type);
    -- The transaction is timestamped with the help of the clock.
    function Date Of (Transaction: Transaction_Type) return Date_Type;
    function Amount Of (Transaction: Transaction_Type) return Money_Type;
    function Source Of (Transaction: Transaction_Type) return Account_Ref;
    function Dest Of (Transaction: Transaction_Type) return Account_Ref;
private
    type Transaction_Type (Source: in Account_Ref; Dest: in Account_Ref) is tagged limited
        record
            Date: Date_Type := Ada. Calendar.Cleck;
            Amount: Money_Type;
        end record;
end Transactions;

6.5 Customer 类映射

with Sets_G; with Transactions; use Transactions;
package Transaction_Sets is new Sets_G (Transaction_Ref);
with Ada. Finalization; use Ada. Finalization;
with Persons; use Persons;
with Accounts.Savings; use Accounts.Savings;
with Accounts.Checking; use Accounts.Checking;
with Transactions; use Transactions; with Transaction_Sets;
package Customers is
    -- 此处代码未完整给出

6.6 Customer Services 类映射

由于系统中只有一个 Customer Services 对象,因此可以使用带有状态的包(即抽象状态机)而不是类型来实现它,其映射过程相对直接,具体代码未展示。

以下是一个表格,总结银行交易案例中各个类的映射情况:
| 类名 | 映射类型 | 关键代码 |
| ---- | ---- | ---- |
| Person | 开放类型 | package Persons 中的相关代码 |
| Account | 抽象数据类型 | package Accounts 中的相关代码 |
| Checking | 从 Account 派生 | package Accounts.Checking 中的相关代码 |
| Transaction | 使用判别式和返回对象访问值的函数 | package Transactions 中的相关代码 |
| Customer | 包含集合属性,控制账户生命周期 | package Customers 中的相关代码 |
| Customer Services | 抽象状态机 | 未展示具体代码 |

通过以上对银行交易案例的映射,我们可以看到如何将面向对象的设计准确地转换为 Ada 代码,同时遵循前面提到的各种原则和方法,包括类关系处理、相互依赖关系解耦、类描述映射、集合映射以及方法体实现等方面的内容。这样的映射过程有助于提高代码的可维护性、可扩展性和一致性,同时充分利用 Ada 语言的特性。

7. 银行交易案例映射的详细分析

7.1 Person 类

Person 类被映射为一个“开放”类型,在 Persons 包中定义。它包含基本的个人信息,如姓名和地址。以下是其代码结构:

package Persons is
    type Name_Type...; type Address_Type...;
    type Person_Type is abstract tagged limited
        -- could be non limited, and even non abstract
        record
            Name: Name_Type;
            Address: Address_Type;
        end record;
    type Person_Ref is access all Person_Type'Class;
end Persons;

这个类的设计允许在不同的场景中灵活使用,既可以作为抽象类型,也可以根据需要调整为非抽象和非受限类型。

7.2 Account 类

Account 类是一个抽象数据类型,在 Accounts 包中实现。它具有账户的基本属性,如账户号码、余额和所有者。以下是其详细代码:

with Persons; use Persons;
package Accounts is
    type Account_Number_Type is ...;
    type Money_Type is ...; type Rate_Type is...;
    type Account_Type is abstract tagged limited private;
    procedure Create (Account: in out Account_Type; Owner: in Person_Ref);
    -- Can be called only once. A check is made that Owner is not null,
    -- and that s/he is in Customer'Class.
    -- The account then gets a unique account number.
    function Owner_Of (Account: Account_Type)return Person_Ref;
    function Account_Number (Account: Account_Type) return Account_Number_Type;
    function Check Balance (Account: Account_Type) return Money_Type;
    procedure Deposit_Cash (Account: in out Account_Type; Amount: in Money_Type);
    procedure Withdraw Cash (Account: in out Account_Type; Amount: in Money_Type);
    procedure Transfer (Source: in out Account_Type; Dest: in out Account_Type'Class; Amount: in Money_Type);
    -- Dest must not be limited to the specific type!
    Overrun Error: exception;
private
    procedure Deposit (Account: in out Account_Type; Amount: in Money_Type);
    procedure Withdraw (Account: in out Account_Type; Amount: in Money_Type) is abstract;
    -- Raises Overrun Error if withdrawing is impossible.
    type Account_Type is abstract tagged limited
        record
            Owner: Person Ref;
            Number : Account_Number_Type := 0;
            Balance: Money_Type := 0.0;
        end record;
end Accounts;

Create 过程用于创建账户,会进行必要的检查,确保所有者不为空且属于 Customer 类,并为账户分配唯一的账户号码。 Deposit_Cash Withdraw Cash 方法用于处理现金的存入和取出, Transfer 方法用于在不同账户之间进行转账。

7.3 Checking 类

Checking 类继承自 Account 类,在 Accounts.Checking 子包中定义。它增加了信用额度的属性,以下是其代码:

package Accounts.Checking is
    type Checking_Type is new Account_Type with private;
    type Checking_Ref is access all Checking_Type'Class;
    procedure Set Credit Limit (Account: in out Checking_Type; Amount: in Money_Type);
    function Credit Limit Of (Account: Checking_Type) return Money_Type;
    Overrun_Error: exception renames Accounts.Overrun_Error;
private
    procedure Withdraw (Account: in out Checking_Type; Amount: in Money_Type);
    -- Raises Overrun Error if withdrawing would exceed the credit limit.
    type Checking_Type is new Account_Type with record
        Credit_Limit: Money_Type := 0.0;
    end record;
end Accounts.Checking;

Set Credit Limit 方法用于设置信用额度, Credit Limit Of 方法用于获取当前的信用额度。在进行取款操作时,如果超过信用额度,会抛出 Overrun_Error 异常。

7.4 Transaction 类

Transaction 类在 Transactions 包中定义,使用判别式来处理常量对象属性。以下是其代码:

with Accounts; use Accounts;
package Transactions is
    subtype Date_Type is Ada. Calendar.Time;
    type Transaction_Type(Source: in Account_Ref; Dest: in Account_Ref) is tagged limited private;
    type Transaction_Ref is access all Transaction_Type'Class;
    procedure Create (Transaction: out Transaction_Type; Amount: in Money_Type);
    -- The transaction is timestamped with the help of the clock.
    function Date Of (Transaction: Transaction_Type) return Date_Type;
    function Amount Of (Transaction: Transaction_Type) return Money_Type;
    function Source Of (Transaction: Transaction_Type) return Account_Ref;
    function Dest Of (Transaction: Transaction_Type) return Account_Ref;
private
    type Transaction_Type (Source: in Account_Ref; Dest: in Account_Ref) is tagged limited
        record
            Date: Date_Type := Ada. Calendar.Cleck;
            Amount: Money_Type;
        end record;
end Transactions;

Create 过程用于创建交易记录,并使用时钟为交易添加时间戳。 Date Of Amount Of Source Of Dest Of 方法分别用于获取交易的日期、金额、源账户和目标账户。

7.5 Customer 类

Customer 类在 Customers 包中定义,包含集合属性 Transactions ,并控制其拥有的账户的生命周期。以下是其部分代码:

with Sets_G; with Transactions; use Transactions;
package Transaction_Sets is new Sets_G (Transaction_Ref);
with Ada. Finalization; use Ada. Finalization;
with Persons; use Persons;
with Accounts.Savings; use Accounts.Savings;
with Accounts.Checking; use Accounts.Checking;
with Transactions; use Transactions; with Transaction_Sets;
package Customers is
    -- 此处代码未完整给出

Customer 类通过封装账户的引用在受控类型中,确保账户不会比其所有者的生命周期更长。

7.6 Customer Services 类

由于系统中只有一个 Customer Services 对象,使用带有状态的包(抽象状态机)来实现它。其映射过程相对直接,虽然具体代码未展示,但主要功能是生成月度报告。

以下是一个 mermaid 流程图,展示银行交易的主要流程:

graph LR;
    A[客户] --> B[账户操作];
    B --> C{操作类型};
    C -- 存款 --> D[Deposit_Cash];
    C -- 取款 --> E[Withdraw Cash];
    C -- 转账 --> F[Transfer];
    D --> G[更新账户余额];
    E --> H{是否超限额};
    H -- 是 --> I[抛出 Overrun Error];
    H -- 否 --> G;
    F --> J[更新源账户和目标账户余额];
    K[交易记录] --> L[Transaction 类];
    M[客户服务] --> N[Generate_Monthly_Reports];

8. 总结与建议

8.1 总结

通过以上对面向对象设计到 Ada 代码的映射过程的详细介绍,我们可以看到在处理类关系、相互依赖关系、类描述映射、集合映射以及方法体实现等方面的重要原则和方法。在银行交易案例中,我们展示了如何将各个类准确地转换为 Ada 代码,同时遵循了提高代码可维护性、可扩展性和一致性的目标。

8.2 建议

  • 设计阶段 :在设计类时,要充分考虑类之间的相互依赖关系,尽量减少循环依赖,提高代码的模块化程度。对于可能出现的相互依赖关系,提前规划好解耦的方法。
  • 命名约定 :遵循统一的命名约定,确保代码的可读性和可维护性。使用类名的复数形式作为包名,后缀作为类型名,变量、组件和参数使用“类”名,保持项目内的一致性。
  • 代码实现 :在实现类时,将类作为抽象数据类型,提供完整的构造函数、修改器和选择器操作。对于集合属性,使用引用而不是对象本身,以保留对象的身份。在处理方法体时,利用对象交互图中的信息,选择合适的迭代模式和错误处理方式。

以下是一个表格,总结不同类的特点和建议:
| 类名 | 特点 | 建议 |
| ---- | ---- | ---- |
| Person | 开放类型,包含基本个人信息 | 可根据具体需求调整为非抽象和非受限类型 |
| Account | 抽象数据类型,账户基本属性和操作 | 确保 Create 方法的检查逻辑正确,处理好异常情况 |
| Checking | 继承自 Account,增加信用额度属性 | 注意取款时的信用额度检查,合理处理异常 |
| Transaction | 使用判别式和返回对象访问值的函数 | 确保交易记录的时间戳准确,各方法功能正确 |
| Customer | 包含集合属性,控制账户生命周期 | 封装账户引用,确保账户与客户生命周期同步 |
| Customer Services | 抽象状态机,生成月度报告 | 确保报告生成逻辑正确,提高性能 |

通过遵循这些原则和建议,可以更好地将面向对象的设计转换为高质量的 Ada 代码,充分发挥 Ada 语言的优势。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值