目录
介绍
规格类型
模块
没有模块开发
模块化编程
谓词和类型
契约
类型化编程
接口和抽象
其他语言的签名
介绍软件设计提升了一系列编程质量:
- 抽象:表示程序各部分的能力,包括其基本特征的摘要。
- 封装:暴露公共部分和隐藏抽象私有部分的可能性。
- 模块化:将程序分解为一组内聚和松散耦合组件的能力。
一些目标:
- 可维护性:修改和扩展程序是否容易?
- 可重用性:是否可以避免代码重复?
模块化有助于拥有可扩展、可修改、可移植、可维护、可重用、易懂和灵活的软件。它允许在需要时无缝添加新功能,并删除不需要的功能,从而简化面向用户的软件视图。它允许多个开发人员同时处理不同的模块。它还允许独立测试模块。此外,大型项目变得更容易监控和控制。
C#,如C,Java和其他编程语言允许通过不同方式创建模块化软件。在本文中,我们将通过C#中的接口简要探讨模块化软件开发。
规格类型代码规格可能采用不同的形式:
- 函数式规格:
- 应该可以定义包含任何类型的相同对象的集合。
- 应该可以在集合中添加和删除元素,并判断元素是否属于集合。
- 非函数式规格:
- 实现应该在当前计算机上有效运行。
- 实现应该正确处理任意大小的集合。
这里出现了验证问题:如何检查规格?
以下是接口/类型规格:
public interface ISet{
ISet Add(ISet s, T e);
ISet Remove(ISet s, T e);
ISet Empty();
bool IsEmpty(ISet s);
bool Find(ISet s, T e);
int Length(ISet s);
}
以下是正式规格:
IsEmpty(Empty()) = true
IsEmpty(Add(s, e)) = false
Find(Empty(), e) = false
Find(Add(s, e), e) = true
Find(Add(s, e), e) = Find(s, e)
Add(s, e) = s [if Find(s, e) = true]
[unconstrained otherwise]
Remove(Empty(), e) = Empty()
Remove(Add(s, e), e) = s
Remove(Add(s, e), f) = Add(Remove(s, f), e)
模块
模块是表示代码单元(类型,值,函数和语言允许的任何表达式)并满足以下条件的构造:
- 接口:模块可以公开提供并需要的组件集合。
- 封装:模块可以隐藏或抽象其某些组件。
模块集应根据其接口引起的依赖关系连接:
- 独立性:模块应仅依赖于其依赖关系的接口。
许多编程语言提供的模块仅适用于这些属性的子集:
- 接口可以提供不同级别的验证:仅限名称(Racket,Python),类型(C,OCaml)。
- 封装可能不存在(参见Python模块privates)。
- 接口有时必须随模块一起提供(Racket,Haskell)或作为独立公民(C,OCaml)。
在下面的文章中,我们将研究C#在模块方面的能力。
没有模块开发所有代码都写在一个单元中,几乎没有规格。
以下是一个例子:
public T[] Empty(){
return new T[]{};
}
public bool IsEmpty(T[] s){
return s.Length == 0;
}
public T[] Add(T[] s, T e){
var l = new List(s);
l.Add(e);
return l.ToArray();
}
public T[] Remove(T[] s, T e){
var l = new List(s);
l.Remove(e);
return l.ToArray();
}
public bool Find(T[] s, T e){
return s.Contains(e);
}
public int Length(T[] s){
return s.Length;
}
有利的特点:
- REPL(简化增量开发)。
- 可扩展语言(简化了编写ad-hoc代码)。
问题:
- 具有不同目的的部分代码都混合在一个文件中(例如:实现和测试)。
- 阻碍代码重用/修改子集(例如,修改集合表示)。
- 使代码验证变得复杂(全部或什么也没有)。
- 不适合单独编译,单独测试,团队开发。
模块化编程将代码分解为一组内聚和松散耦合的模块,这些模块应根据规格进行组合。
接口/实现类的关系类似于模块/签名。
以下是接口/类型规格:
public interface ISet{
ISet Add(ISet s, T e);
ISet Remove(ISet s, T e);
ISet Empty();
bool IsEmpty(ISet s);
bool Find(ISet s, T e);
int Length(ISet s);
}
以下是客户端模块:
public class MySet:ISet{
public ISet Add(ISet s, T e) { /* ... */ }
public ISet Remove(ISet s, T e) { /* ... */ }
public ISet Empty() { /* ... */ }
public bool IsEmpty(ISet s) { /* ... */ }
public bool Find(ISet s, T e) { /* ... */ }
public int Length(ISet s) { /* ... */ }
}
以下是测试模块:
public class MySetTest{
public void AddTest() { /* ... */ }
public void RemoveTest() { /* ... */ }
public void EmptyTest() { /* ... */ }
public void IsEmptyTest() { /* ... */ }
public void FindTest() { /* ... */ }
public void LengthTest() { /* ... */ }
}
好处:
- 促进代码重用:测试和客户端都可以使用相同的实现模块。
- 允许多个实现:客户端可以选择适当的实现模块而无需修改其代码。
- 确保有效提供所需的功能。
- 允许多个开发人员同时处理不同的模块。
- 允许独立测试模块。
- 大型项目变得更容易监控和控制。
类型是语言值的子集。例如:bool 类型是集合{ true,false}。
谓词是一个取任何可能值并返回布尔值的函数:
- 谓词是类型的特征函数。
- 谓词通常是具有动态类型检查的语言的符号。
在C#中,根据官方文档,契约提供了一种为代码指定前置条件,后置条件和对象不变量的方法。前提条件是输入方法或属性时必须满足的要求。后置条件描述了方法或属性代码退出时的期望。对象不变量描述处于良好状态的类的预期状态。
代码契约包括用于标记代码的类,用于编译时分析的静态分析器和运行时分析器。代码契约的类可以在System.Diagnostics.Contracts命名空间中找到。
代码契约的好处包括:
- 改进的测试:代码契约提供静态契约验证,运行时检查和文档生成。
- 自动测试工具:您可以使用代码契约通过过滤掉不符合前提条件的无意义测试参数来生成更有意义的单元测试。
- 静态验证:静态检查程序可以在不运行程序的情况下决定是否存在任何契约违规。它检查隐式契约,例如null解引用和数组边界,以及显式契约。
- 参考文档:文档生成器使用契约信息扩充现有XML文档文件。还有可以与Sandcastle一起使用的样式表, 以便生成具有契约部分的文档页面。
类型系统是应用于程序的形式方法,其旨在用类型对程序的元素进行分类,以保证其行为的某些正确性。
根据语言及其编译器,类型系统可能有不同的风格:
- 动态类型检查:所有验证都在运行时完成,不需要类型注释(Racket,Python,Ruby)。
- 静态类型检查:所有验证都在编译时完成,类型注释要么是必需的(C#,C,Java),要么是推断的(OCaml,Haskell)。
好处:
- 静态检查类型。
- Typed类型系统非常具有表现力。
抽象数据类型是:
- 组件的抽象和独立表示。
- 可能是同一抽象的多个不同实现。
- 可能是同一抽象的多个客户端。
Java中的接口:
interface Set{
Set add(Set set, T e);
Set remove(Set set, T e);
Set empty();
boolean is_empty(Set set);
boolean find(Set set, T e);
int length(Set set);
}
OCaml中的签名:
module type SET = sig
type ’a set
val add : ’a set → ’a → ’a set
val remove : ’a set → ’a → ’a set
val empty : unit → ’a set
val is_empty : ’a set → bool
val find : ’a set → ’a → bool
val length : ’a set → int
end
原文地址:https://www.codeproject.com/Articles/5163072/Modular-Software-Development-In-Csharp