飞哥的海

导航

Essential.C#--第五章 类

    在第一章中你已经看到了如何声明一个新类Helloworld。在第二章,你也也学习了C#内建的原类型。你也知道了控制流,还有如何声明方法。现在是时候讨论如何定义属于自己的类型了。这在任何C#程序都是核心结构。C#作为一个面向对象的语言是完全支持类和构建对象。

image

 

    本章为你引入c#的面向对象编程方式。重点是如何定义类,这是对象的模板。

    在前面的章节的程序结构一直都是面向对象的编程。不管怎样,用来包裹这些构造,你就能创建更大,更有组织的程序,并具有可维护性。从结构化,控制流程编程过渡到面向对象编程是革命性的变革。因为这提供了额外的组织形式。这带来的结果就是,小程序变的有点简单,但更重要的,这将能创建更大的程序,因为编程代码被更好的组织起来。

    面向对象编程还有一个关键的特性是,避免从零编写一个新程序,你能收集从前工作中的对象,并扩展类的新特性,添加更多类,然后用新功能重组。

    本章深入讨论C#是如何支持封装的。比如,类型,属性,还有访问修饰符。下一章在本章基础上,介绍继承和多态。

 

面向对象的编程

    今天程序能成功的关键是,在越来越大的应用中用结构化的方式实施复杂的需求。面向对象的编程就提供了一套实现方法。预想将面向对象的程序转换成结构程序是很困难的。除了某些无意义简单的程序。

    面向对象编程最基本的结构式类和对象。这是对现实世界的模块、模板概念的在编程中的抽象形式。比如,类型OpticalStorageMedia有一个方法Eject()用于从播放器中弹出CD/DVD。类OpticalStorageMedia就是对真实世界的程序化的抽象。

    类依据三个面向对象的原则:封装,继承和多态。

 

封装

    封装是将细节隐藏,当需要时就可以访问这些细节,对细节的合理封装,很容易让大程序理解。无意中修改被保护的数据,由于改变代码是在封装的边界内,代码就可以很容易保持。方法就是封装的例证。尽管可以将方法中的代码直接嵌入调用者代码中,不过,将代码重构编入方法提供了有益的封装。

  

继承

    看看下面的例子,DVD是光学媒体的一种。它可以存储数字影片。CD是另一种有不同性质的光学媒体。它们两者的版权实现是不同的,并且存储容量也不同。不同的存储介质,都有特定的特性,不过都会有些基本功能,比如,支持文件系统,媒体文件是只读还是可写。

    如果你为每个存储媒体定义一个类型。你就要定义一个类继承。这是一系列的is a关系。基础类是所有存储介质驱动器的源头。它可以是StorageMedia类。比如,CD,DVD,硬盘,软驱,等等都是StorageMedia类型。然而,CD和DVD并需要直接源自StorageMedia。而它们是源自一个中间类型,OpticalStorageMedia。你可以用UML来查看类继承图。

image

    继承关系需要至少两个类,其中一个是更普遍的版本。在图5.1中,硬盘是更具体的版本,尽管HardDrive有许多特殊类型,它依然是StorageMedia。反之则说不通。StorageMedia类型不一定是hardDrive类型。图5.1中的继承关系中就包含了多个类。

    特定类型是衍生类型或叫子类。而通用的类型叫基类或超类。在继承关系中类的另一种较常用的术语是父亲和子孙。前者是更通用的类。

    从另一个类衍生或继承到特定的类型,是指自定义基类以便能面向特定用途。同理,基类是衍生类的通用实现。

    继承的关键是,所有衍生类集成基类的成员。通常,基类的成员实现能够被修改。衍生类能包含基类的成员。

    衍生类允许你以层级的关系组织你的类,孩子类会有比父亲类更多的特性。

 

多态

    多态包含两个意思,一个是“多”另一个是“形式”。在对象的上下文中,多态的意思就是一个简单的方法或类型能有多种实现。如果你想有一个媒体播放器。它能播放音乐CD,DVD还有MP3.然而,这就需要依赖不同媒体类型精确实现Play()方法。在CD对象上调用Play()或在DVD播放,这都会播放音乐。所有的类型都知道其基类,OpticalStorageMedia,并都有各自的方法声明。多态是一个规范,类型能处理方法实现的细节,因为,在多个衍生类中都有方法出现,它们都共享基类中的同样的方法声明。

 

定义和类实例

     定义一个类,首先用关键字class,后跟标示符。

清单5.1
————————————————————————
class Employee
{
}
————————————————————————

类中的代码都要包含在类声明之后的花扩号中。虽然这不是必须做的,但通常将类放入各自的文件中。这样就能很容易的找到特定类。习惯上,类名就是文件名。

    一旦你定义了一个新类,你就能将它构建入framework中。换句话说,你能声明一个此类型的变量,或将这个新类作方法参数传递。

class Program
{
    class Employee
    { 
        //...
    }
 
    static void Main()
    {
        Employee employee1, employee2;
    }
 
 
    static void IncreaseSalary(Employee employee)
    {
        // ...
    }
}

类声明下面的花括号划分出执行界限。

 

 

定义对象和类

    在非正式的讨论中,对象和类是可以混用的。然而,对象和类却有着不同的意思。类是一个对象在运行期的模板。对象是一个类的实例。类就像一个模具,将会创建一个部件。对象就是模具对于的部件。从类创建对象的过程叫实例化,因为对象是类的实例。

    你已经定义了一个新类,现在是实例化这个类型的时候了。C#使用new关键字类实例化一个对象

清单5.3

class Program
{
    class Employee
    {
        //...
    }
 
    static void Main()
    {
        Employee employee1 = new Employee();
        Employee employee2;
        employee2 = new Employee();
        IncreaseSalary(employee1);
    }
 
    static void IncreaseSalary(Employee employee)
    {
        // ...    
    }
}

不要吃惊,赋值能和声明语句在同一行,也可以分开操作。

    不像原始类型,并没有直接指定Employee。代替的操作是,用new操作符来在运行时为Employee对象分配内存,实例化这个对象,并返回此实例的引用。

    尽管分配内存是显式的,但并没有相应的回收内存的操作符。在程序结束以前,自动回收对象内存。垃圾回收器负责内存自动分配。由它确定没有被活动对象引用的对象,然后重新将内存分配给别的对象。内存会被系统重新分配,它并不是在编译时定位。

 

封装的第一季:用方法分组对象数据。

    如果你收到用员工第一个名字做索引的卡片,用员工第二个名字做索引的卡片,还有一个用他们薪水做索引的卡片。这些卡片没什么价值,unless you knew that the cards were in order in each stack。所以,为了得到员工的全名就需要搜索两个卡片。

    在非面向对象的语言上下文中,将一组项目装入一个封套中,同样面向对象编程将方法和数据一起装入对象。提供了一组类成员以便不再单独处理。

 

实例化字段

    面向对象设计的一个关键目的就是将数据分组。本节讨论如何将数据加入类。在面向对象的术语中,类中存储数据的变量叫做成员变量,此术语仅对C#有效。更为精确的术语是字段,用内置类型为存储数据的单元命名。实例化字段是在类层次为对象要存储的数据声明一个变量。因此,联合就是字段类型和字段之间的关系。

 

声明一个实例字段

    在清单5.4中,Employee已经被修改成包含三个字段的类:FirstName,LastName,Salary

清单5.4

class Employee
{
    public string FirstName;
    public string LastName;
    public string Salary;
}

对于增加的这些字段,employee类的实例可以存储一些基本数据。所以这些字段都用了public访问修饰符。字段上的public指示了除了employee以外别的类也可以访问这些字段。

     和本地变量声明一样,一个字段的声明包含和字段相关的数据类型。此外,在声明期,会给字段分配一个初始值。

清单5.5

class Employee
{
    public string FirstName;
    public string LastName;
    public string Salary = "Not enough";
}

 

访问一个实例字段   
    你可以设置或检索字段内的数据。然而,字段是不能包含static修饰符的。你能从对象上访问一个实例字段,而不能通过类直接访问。

   

实例方法

    在类中提供了一个处理员工姓名格式化的方法,来替代main()方法中的WriteLIne()方法。这样做是符合类封装性的。

 

使用this关键字

    你能获得类的实例成员引用的类。为了明确指示能访问的字段或方法。你需要使用this、this是一个简单内含字段,它返回对象本身。

class Employee
{
    public string FirstName;
    public string LastName;
    public string Salary;
    public string GetName()
    {
        return FirstName + " " + LastName;
    }
    public void SetName(string newFirstName, string newLastName)
    {
        this.FirstName = newFirstName;
        this.LastName = newLastName;
    }
}
 

 

   

用代码格式避免歧义

    在SetName()方法中,你不用使用this关键字,因为FirstName和newFirstName是显然不一样的。考虑另外一种情况,如果用FirstName代替newFirstName

清单5.10

class Employee
{
    public string FirstName;
    public string LastName;
    public string Salary;
    public string GetName()
    {
        return FirstName + " " + LastName;
    }
    // Caution:  Parameter names use Pascal case
    public void SetName(string FirstName, string LastName)
    {
        this.FirstName = FirstName;
        this.LastName = LastName;
    }
}

 

 

文件存储和载入

 

 

封装第二季:信息隐藏

    除了将数据和方法一起封装入一个简单的单元。封装还可以隐藏一个对象的数据和行为的内部细节。在一定程度上,方法也是做这个事。方法声明是对所有调用者可见的。而内部实现是隐藏的。面向对象的编程也具有这个特性,然而,它提供了一个工具用来控制成员在类外部的现实范围。不将成员公开给类外部是私有成员。

    在面向对象的编程中,封装不仅仅是分组数据和行为的术语,它还可以将数据隐藏在类内部以便最小限度的曝光类的访问。

    访问修饰符的作用就是封装。使用public,你明确的指示了可以从Employee类的外部修改字段。换句话说,可以从Program类访问Employee类。

    为了将Password字段隐藏,不让别的类触及到。你要使用private访问修饰符。这样在Program类中你就不能访问到Password字段了。

    注意当没有为成员指定访问修饰符,默认的就是private。换句话说,成员默认是私有的。程序员需要明确指定成员为公有。

 

属性

    在上一节,访问修饰符,演示了,如何使用private关键字封装一个密码,阻止从类的外部访问。这样的封装太彻底了。比如,有时你想定义一个字段,外部类只能读它,并不能改变它的值。还有,你可能要向类中写入某些数据,但你想验证数据。有太多的例子需要即时创建数据。

    通常,编程语言会有这样的特性,将一个字段值为私有,然后为访问和修改数据分别提供getter和setter方法。

清单5.16

class Employee
{
    private string FirstName;
    // FirstName getter
    public string GetFirstName()
    {
        return FirstName;
    }
    // FirstName setter
    public void SetFirstName(string newFirstName)
    {
        if (newFirstName != null && newFirstName != "")
        {
            FirstName = newFirstName;
        }
    }
    private string LastName;
    // LastName getter
    public string GetLastName()
    {
        return LastName;
    }
    // LastName setter
    public void SetLastName(string newLastName)
    {
        if (newLastName != null && newLastName != "")
        {
            LastName = newLastName;
        }
    }
    // ...
}
 

这一改变,会影响编程的方式,你不再需要使用赋值操作符设置数据。

 

声明属性

    C#为这种模式提供了实现语法。这种语法叫做属性

 

自动实现属性

    C#3.0中,属性语法中包含一个精简版本。

 

命名习惯

 

用属性校验

 

只读属性和只写属性

 

在Get和Set上的访问修饰符

 

属性作为虚字段

 

 

静态

静态字段

    为了定义一个在多个实例中都可以访问到的数据,需要使用static关键字。

在这个例子中,NextId字段声明包含static修饰符,所以这就叫做静态字段。而Id就是一个单纯存储位置。NextId在Employee类的实例中共享数据。

 

扩展方法

posted on 2009-09-04 11:24  飞哥的海  阅读(336)  评论(0编辑  收藏  举报