第3章 类型设计规范

ü 要确保每个类型由一组定义明确、相互关联的成员组成。

3.1. 类型和名字空间

ü 要用命名空间把类型组织成一个相关的特性域的层次结构。

 

û 避免非常深的名字空间层次。这样的层次难于浏览,因为用户不得不经常地回溯。

 

û 避免有太多的名字空间。

 

û 避免把为高级场景而设计的类型和为常见编程任务而设计的类型放在同一个名字空间中。

 

û 不要不指定名字空间就定义类型。

3.2. 类和结构之间的选择

ü 要深入了解引用类型和值类型在行为上的差异。

 

ü 作为一条经验法则,框架中的大多数类型应该是类。但是在有些情况下,由于值类型所具备的特征,使用结构会更为合适。

 

ü如果该类型的实例比较小而且生命期比较短,或者经常被内嵌在其他对象中,考虑定义结构而不要定义类。

 

û 不要定义结构,除非该类型具有以下所有特征:

Ø 它在逻辑上代表一个独立的值,与基本类型(intdouble等等)相似。

Ø 它的实例的大小小于16个字节。

Ø 它是不可变的。

Ø 它不需要经常被装箱。

在所有其他情况下,应该将类型定义为类。

3.3. 类和接口之间的选择

ü 要优先采用类而不是接口。

说明:与基于接口的API相比,基于类的API容易演化得多,因为可以给类添加成员而不会破坏已有的代码。

 

ü 要用抽象类而不是用接口来解除协定与实现之间的耦合。

说明:抽象类经过正确的设计,同样能够解除协定与实现之间的耦合,与接口所能达到的程度不相上下。

 

ü 如果需要提供一个多态的值类型层次结构的话,要定义接口。

说明:值类型不能来自其他类型继承,但它们可以实现接口。例如,IComparableIFormat table以及IConvertible都是接口,因此Int32Int64等值类型及其他基本类型,都可以是

comparableformattableconvertible的。

public struct Int32 : IComparableIFormattableIConvertible{}

public struct Int64 : IComparableIFormattableIConvertible{}

 

ü 考虑通过定义接口来达到与多重继承相类似的效果。

3.4. 抽象类的设计

û 不要在抽象类型中定义公有的或内部受保护的( protected-internal)构造函数。

说明:

只有当用户需要创建一个类型的实例时,该类型的构造函数才应该是公有的。由于你

无法创建一个抽象类型的实例,因此如果抽象类型具有公有构造函数,那么这样的设

计不仅是不当的,而且还会误导用户。

// bad design

public abstract class Claim

{

public Claim(int number){}

}

// good design

public abstract class Claim

{

// incorrect Design

protected Claim(int number){}

}

 

ü 要为抽象类定义受保护的构造函数或内部构造函数。

说明:受保护的构造函数更为常见,它仅仅是允许当子类型被创建时,基类能够做自己的初始化。

public abstract class Claim

{

protected Claim() { }

}

内部构造函数可以用来把该抽象类的具体实现限制在定义该抽象类的程序集中。

public abstract class Claim

{

internal Claim() { }

}

 

ü 要为你发布的抽象类提供至少一个继承自该类的具体类型。

说明:这有助于验证该抽象类的设计是否正确。例如,System.IOFileStreamSystem.IOStream抽象类的一个实现。

3.5. 静态类的设计

ü 要尽量少用静态类。

 

û 不要把静态类当做杂物箱。每一个静态类都应该有其明确的目的。

 

û 不要在静态类中声明或覆盖实例成员。

 

ü 要把静态类定义为密封的、抽象的,并添加一个私有的实例构造函数——如果你的编程语言没有内置对静态类的支持。

3.6. 接口的设计

ü 要定义接口,如果你需要包括值类型在内的一组类型支持一些公共的API

 

ü 考虑定义接口,如果你需要让已经自其他类型继承的类型支持该接口提供的功能。

 

û 避免使用记号接口(没有成员的接口)。如果你需要给一个具备某特征(记号)的类做记号,一般来说,最好使用自定义attribute而不要使用接口。

 

ü 要为接口提供至少—个实现该接口的类型。

例如:这有助于验证接口的设计。例如,System.Collections.ArrayListSystem.Collections.IList接口的一个实现。

 

ü 要为你定义的每个接口提供至少一个使用该接口的API(一个以该接口为参数的方法,或是一个类型为该接口的属性)。这有助于有助于验证接口的设计。

例如:List<T>.Sort使用了IComparer<T>接口。

 

û 不要给已经发行的接口再添加成员。

说明:这样做会破坏该接口的实现。为了避免版本的问题,应该创建一个新的接口。

3.7. 结构的设计

û 不要为结构提供默认的构造函数。

 

ü 要确保当所有的实例数据都为零、falsenull(如果合适)时,结构仍处于有效状态。这可以防止在创建一个结构的数组时创建出无效的实例。

例如:下面的结构设计得不正确。带参数的构造函数有意用来确保状态有效,但是在创建该结构的数组时,这个构造函数没有被执行,因此所有的实例字段都被初始化为0,而对该类型来说这并不是一个有效的状态。

//bad Design

public struct PositiveInteger

{

int value;

public PositiveInteger(int value)

{

if (value <= 0)

throw new ArgumentException(); }

this.value = value;

}

public override string ToString()

{

return value.ToString();

}

}

 

通过确保默认的状态(本例中的value宇段为0)是该类型的有效逻辑状态,这个问题

可以得到解决。

//good Design

public struct PositiveInteger

{

int value;

public PositiveInteger(int value)

{

if (value <= 0)

throw new ArgumentException(); }

this.value = value - 1;

}

public override string ToString()

{

return (value + 1).ToString();

}

}

 

ü 要为值类型实现IEquatable<T>

说明:值类型的ObjectEquals方法会导致装箱,而且它的默认实现也并不非常高效,因为它使用了反射。IEquatable<T>Equals的性能要好得多,而且能够实现为不会导致装箱。

 

û 不要显式地扩展System.ValueType,事实上大多数编程语言不允许这样做。

3.8. 枚举的设计

ü 要用枚举来加强那些表示值的集合的参数、属性以及返回值的类型性。

 

ü 要有限使用枚举而不要使用静态常量。

//Avoid the following

public static class Color

{

public static int Red = 0;

public static int Green = 1;

public static int Blue = 2;

}

//Favor the following

public enum Color

{

Red,

Grean,

Blue

}

 

û 不要把枚举用于开放的集合(比如操作系统的版本、朋友的名字等)

 

û 不要提供为了今后使用而保留的枚举值。

 

û 避免显式暴露只有一个值的枚举。

 

û 不要把sentinel值包含在枚举值中。

例如,下面的代码显示了一个带sentinel值的枚举,这个附加的sentinel值用来表示枚举的最后一个值,其目的是用于范围检查。在框架设计中,这是不好的做法。

public enum DeskType

{

Circular = 1,

Oblong = 2,

Rectangular = 3,

LastValue = 3 //this sentinel should not be here

}

 

public void OrderDesk(DeskType desk)

{

if (desk > DeskType.LastValue)

throw new ArgumentOutOfRangeException(); }

}

 

ü 框架的开发人员应该用真实的枚举值来执行检查,而不应该依赖于sentinel值。

public void OrderDesk(DeskType desk)

{

if (desk > DeskType.Rectangular||desk<DeskType.Circular)

throw new ArgumentOutOfRangeException(); }

}

 

ü 要为简单枚举类型提供零值。

例如:可以考虑把该值称为“None”之类的东西。如果这样的值不适用于某个特定的枚举那么应该把该枚举中最常用的默认值赋值为零。

public enum Compression

{

None = 0,

GZip,

Deflate

}

public enum EventType

{

Error = 0,

Warning,

Information

}

 

ü 考虑用Int32(大多数编程语言的默认选择)作为枚举的基本实现类型,除非下面的

任何一条成立:

Ø 该枚举是一个标记枚举,而你有超过32个标记,或预计今后会有更多的标记。

Ø 为了更方便地与需要不同大小的枚举的非托管代码进行互操作,基本的实现类型必须是Int32之外的类型。

Ø 更小的底层实现类型可能会节省相当的空间。

 

ü 要用复数名词或名词短语来命名标记枚举,用单数名词或名词短语来命名简单枚举。

 

û 不要直接扩充System.Enum

说明:SystemEnurn是一个特殊的类型,被CLR用来创建用户定义的枚举。大多数编程语言提供了相应的编程元素让你访问这项功能。例如,在C#中,enum关键字被用来定义一个枚举。

3.8.1. 标记枚举的设计

ü 要对标记枚举使用System.FlagsAttribute。不要把该attribute用于简单枚举。

[Flags]

public enum AttributeTargets

{ }

 

ü 要用2的幂次方作为标记枚举的值,这样就可以通过按位或操作自由地组合它们。

[Flags]

public enum WatcherChangeTypes

{

Created = 0x0002,

Deleted = 0x0004,

Changed = 0x0008,

}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值