c#设计思想_c# mvvm设计思想 - CSDN
  • 因此C#将解决方案(solution)放在首位。所谓的namespace就是解决方案的名字,这个解决方案囊括了所有的程序和类。因此,namespace的大括号要把所有的类和函数都括起来。在C#中,是没有单个函数的。所有的函数是一个...
    你编写任何一群代码。都是为了解决 一个问题。因此C#将解决方案(solution)放在首位。所谓的namespace就是解决方案的名字,这个解决方案囊括了所有的程序和类。因此,namespace的大括号要把所有的类和函数都括起来。在C#中,是没有单个函数的。所有的函数是一个本身可以执行的函数,也是所在类的一个方法。即使是Main()函数,也要在一个类里面。
    
    展开全文
  • 为了更好的理解设计思想,结合一个尽可能简洁的实例来说明OOD、设计模式及重构。通过下面的代码,详细地阐述面向对象设计思想。 一、传统过程化设计思想 假定我们要设计一个媒体播放器(只从软件设计的...

    有了思想才能飞翔,缺乏灵活就象少了轮子的汽车,难以飞奔。为了更好的理解设计思想,结合一个尽可能简洁的实例来说明OOD、设计模式及重构。通过下面的代码,详细地阐述面向对象设计思想。

    一、传统过程化设计思想

    假定我们要设计一个媒体播放器(只从软件设计的角度,不涉及硬件)。该媒体播放器目前只支持音频文件mp3和wav。按照结构化设计思想,设计出来的播放器的代码如下:

      

    代码
     1 public class MediaPlayer
     2 
     3 {   
     4 
     5    private void PlayMp3()
     6 
     7    {
     8 
     9       MessageBox.Show("Play the mp3 file.");
    10 
    11    }
    12 
    13  
    14 
    15    private void PlayWav()
    16 
    17    {
    18 
    19       MessageBox.Show("Play the wav file.");
    20 
    21    }
    22 
    23  
    24 
    25    public void Play(string audioType)
    26 
    27    {      
    28 
    29       switch (audioType.ToLower())
    30 
    31       {
    32 
    33           case ("mp3"):
    34 
    35              PlayMp3();
    36 
    37              break;
    38 
    39           case ("wav"):
    40 
    41              PlayWav();
    42 
    43              break;             
    44 
    45       }      
    46 
    47    }
    48 
    49 }
    50 
    51 

     

     

    从传统的过程化设计思想来看,这是一段既实用又简洁的代码。

    如果,客户又提出新的要求:要播放器不仅仅播放mp3和wav文件,还要播放其他音频文件如wma、mp4等,为此我们要不断地增加相应地播放方法和修改条件语句,直止条件语句足够长。

    如果,客户感到这个媒体播放器功能太少了,只能闻其声,不能见其人,太单一。如果在听着优美音乐的同时又能看到歌唱者潇洒、英俊的舞姿那就更好了。从代码设计的角度看,他们希望媒体播放器支持视频文件了。也许你会想,不会再增加视频这方面的代码,可以,在增加视频媒体的播放方法,在修改条件判断语句,如果还有其他,还可以同样地增加、修改。到此你也许会提出,要是不修改或很少修改原来的代码就能增添其他功能该多好啊!

    这样看,原来的软件设计结构似乎有点问题。事实上,随着功能的不断增加,你越来越发现这个设计非常的糟糕,因为它根本没有为未来的需求变更提供最起码的扩展。为了应接不暇的变更需求,你不得不不厌其烦地修改原来的代码,使其适应需求变化,甚至在修改代码时,由于过多的代码依赖关系弄得人焦头烂额,直止一塌糊涂。

    二、面向对象设计思想

    还是以设计一个媒体播放器为例,设计要求相同。不访我们换个设计思路利用面向对象设计思想(OOD)来做做看如何!

    根据OOD的思想,我们应该把mp3和wav分别看作是两个独立的对象。代码设计如下:

      

    代码
     1 public class MP3
     2 
     3 {
     4 
     5    public void Play()
     6 
     7    {
     8 
     9        MessageBox.Show("Play the mp3 file.");
    10 
    11    }
    12 
    13 }
    14 
    15  
    16 
    17 public class WAV
    18 
    19 {
    20 
    21    public void Play()
    22 
    23    {
    24 
    25        MessageBox.Show("Play the wav file.");
    26 
    27    }
    28 
    29 }
    30 
    31  
    32 
    33 Public class MediaPlayer
    34 
    35 {
    36 
    37       switch (audioType.ToLower())
    38 
    39       {
    40 
    41           case ("mp3"):
    42 
    43                      MP3 m = new MP3();
    44 
    45              m.Play();
    46 
    47              break;
    48 
    49           case ("wav"):
    50 
    51              WAV w = new WAV();
    52 
    53 w.Play();
    54 
    55              break;             
    56 
    57       }
    58 
    59 }     
    60 
    61 

     

     

           现在我们重构代码,建立统一的Play()方法,(在后面的设计中,你会发现这样改名是多么的重要!)更改媒体播放类MediaPlayer的代码。如果这样的设计代码,实质上没有多大的变化,只是对原来过程化设计思想的一种替代,并没有击中要害,亦然没有灵活性、可扩展性。

           2.1单向分派技术的应用(在这里用类的多态来实现的)

    我们不访这样设想:既然mp3和wav都属于音频文件,都具有音频文件的共性,应该建立一个共同的AudioMedia父类。

      

     1 public class AudioMedia
     2 
     3 {
     4 
     5    public void Play()
     6 
     7    {
     8 
     9        MessageBox.Show("Play the AudioMedia file.");
    10 
    11    }
    12 
    13 }
    14 
    15 

     

     

    现在引入继承思想,OOD就有点雏形了(不是说有了继承就有了OOD思想,这里只是从继承的角度谈一谈OOD思想,当然从其他角度如合成、聚合等角度也能很好地体现OOD思想)。

    其实在现实生活中,我们的播放器播放的只能是某种具体类型的音频文件如mp3,因此这个AudioMedia类只能是音频媒体的一个抽象化概念,并没有实际的使用情况。对应在OOD设计中,既这个类永远不会被实例化。为此我们应将其改为抽象类,如下:

     

    到此,我们通过单向分派技术使OOD思想得到进一步的体现。现在的设计,即满足了类之间的层次关系,又保证了类的最小化原则,同时又体现了面向对象设计原则(开—闭原则、里氏代换原则)更利于扩展。(止此,你会发现play方法名的更改是多么必要)。

    如果现在又增加了对WMA、MP4等音频文件的播放,只需要设计WMA类,MP4类,并继承AudioMedia,在相应的子类中重写Play方法就可以了,MediaPlayer类对象的Play方法根本不用任何改变。

    如果让媒体播放器能够支持视频文件,必须另外设计视频媒体的类。因视频文件和音频文件有很多不同的地方,不可能让视频继承音频。假设我们播放器支持RM和MPEG格式的视频。视频类代码如下:

     

    代码
     1 public abstract class VideoMedia
     2 {
     3 
     4    public abstract void Play();
     5 
     6 }
     7 
     8 public class RM:VideoMedia
     9 {
    10 
    11    public override void Play()
    12 
    13    {
    14 
    15        MessageBox.Show("Play the rm file.");
    16 
    17    }
    18 
    19 }
    20 
    21 
    22 public class MPEG:VideoMedia
    23 {
    24 
    25    public override void Play()
    26 
    27    {
    28 
    29        MessageBox.Show("Play the mpeg file.");
    30 
    31    }
    32 
    33 }
    34 
    35 

     

     

    这样设计还是有点糟糕,这样就无法实用原有的MediaPlayer类了。因为你要播放的视频RM文件并不是音频媒体AudioMedia的子类。

           不过,我们可以这样想,无论音频媒体还是视频媒体都是媒体,有很多相似的功能,如播放、暂停、停止等,为此我们把“媒体”这个概念抽象出来做为一个接口。(虽然也可以用抽象类,但在C#里只支持类的单继承,不过c#支持接口的多继承)。根据接口的定义,你完全可以将相同功能的一系列对象实现同一个接口。让音频媒体类及视频媒体类都继承媒体这个接口。代码如下:

     

    1 public abstract class VideoMedia:IMedia
    2 {
    3 
    4    public abstract void Play();
    5 
    6 }
    7 
    8 

     

     

     

     

    这样再更改MediaPlayer类的代码:

    现在看来,程序是不是有很大的灵活性和可扩展性了。

     1 public class MediaPlayer
     2 
     3 {  
     4 
     5    public void Play(IMedia media)
     6 
     7    {      
     8 
     9        media.Play();
    10 
    11    }
    12 
    13 }
    14 
    15 

     

          

    总结一下,从MediaPlayer类的演变,我们可以得出这样一个结论:在调用类对象的属性和方法时,尽量避免将具体类对象作为传递参数,而应传递其抽象对象,更好地是传递接口,将实际的调用和具体对象完全剥离开,这样可以很好地体现了软件工程的灵活性、扩展性。

           现在看起来似乎很完美了,但我们忽略了MediaPlayer的调用者这个事实。仍然需要条件语句来实现。例如,在客户端程序代码中,用户通过选择cbbMediaType组合框的选项,决定播放音频媒体还是视频媒体,然后单击Play按钮执行。

      

    代码
    Public void BtnPlay_Click(object sender,EventArgs e)
    {

        IMedia media 
    = null;

        
    switch (cbbMediaType.SelectItem.ToString().ToLower())
        {

            
    case ("mp3"):

                 media 
    = new MP3();

                 
    break;

                         
    //其它类型略;

            
    case ("rm"):

                 media 
    = new RM();

                 
    break;   

            
    //其它类型略;

        }

        MediaPlayer player 
    = new MediaPlayer();

        player.Play(media);

    }

     

     

    2.2设计模式、条件外置及反射技术的应用

    随着需求的增加,程序将会越来越复杂。此时就应调整设计思想,充分考虑到代码的重构和设计模式的应用。最后当设计渐趋完美后,你会发现,即使需求不断增加,你也可以神清气爽,不用为代码设计而烦恼了。

    为了实现软件工程的三个主要目标:重用性、灵活性和扩展性。我们不访用设计模式、条件外置及反射来实现。

    使用工厂模式,能够很好地根据需要,调用不同的对象(即动态调用),保证了代码的灵活性。

    虽然这里有两种不同类型的媒体AudioMedia和VideoMedia(以后可能更多),但它们同时又都实现IMedia接口,所以我们可以将其视为一种产品。媒体工厂接口如下:

     

     

     

    1 public interface IMediaFactory
    2 {
    3 
    4    IMedia CreateMedia();
    5 
    6 }
    7 

     

     

    然后为具体的媒体文件对象搭建工厂,并统一实现媒体工厂接口:

     

    代码
     1 public class MP3Factory:IMediaFactory
     2 
     3 {
     4 
     5    public IMedia CreateMedia()
     6 
     7    {
     8 
     9        return new MP3();
    10 
    11    }
    12 
    13 }
    14 
    15 //其它工厂略;
    16 
    17  
    18 
    19 public class RMFactory:IMediaFactory
    20 
    21 {
    22 
    23    public IMedia CreateMedia()
    24 
    25    {
    26 
    27        return new RM();
    28 
    29    }
    30 
    31 }
    32 
    33 //其它工厂略;
    34 
    35 

     

     

    写到这里,也许有人会问,为什么不直接给AudioMedia和VideoMedia类搭建工厂呢?很简单,因为在AudioMedia和VideoMedia中,分别还有不同的类型派生,如果为它们搭建工厂,则在CreateMedia()方法中,仍然要使用条件判断语句,代码缺乏灵活性,不利扩展。

    还有一个问题,就是真的有必要实现AudioMedia和VideoMedia两个抽象类吗?让其子类直接实现接口不是更简单?对于本文提到的需求,是能实现的。但不排除AudioMedia和VideoMedia它们还会存在其他区别,如音频文件还需给声卡提供接口,而视频文件还需给显卡提供接口。如果让MP3、WAV、RM、MPEG直接实现IMedia接口,而不通过AudioMedia和VideoMedia,在满足其它需求的设计上也是不合理的。现在客户端程序代码发生了稍许的改变:

     

    代码
    Public void BtnPlay_Click(object sender,EventArgs e)
    {

        IMediaFactory factory 
    = null;

        
    switch (cbbMediaType.SelectItem.ToString().ToLower())
        {

           
    //音频媒体

            
    case ("mp3"):

                 factory 
    = new MP3Factory();

                 
    break;

            
    //视频媒体

            
    case ("rm"):

                  factory 
    = new RMFactory();

                 
    break;   

            
    //其他类型略;

        }

        MediaPlayer player 
    = new MediaPlayer();

        player.Play(factory.CreateMedia());

    }

     

     

    到这里,我们再回过头来看MediaPlayer类。这个类中通过单向分派,根据传递参数的不同,分别实现了不同对象的Play方法。在不用工厂模式时,这个类对象会运行得很好。作为一个类库或组件设计者来看,他提供了一个不错的接口,供客户端程序调用。

    利用工厂模式后,现在看来MediaPlayer类已经多余。所以,我们要记住的是,重构并不仅仅是往原来的代码添加新的内容。当我们发现一些不必要的设计时,还需要果断地删掉这些冗余代码。修改后的代码如下:

     

    代码
     1 Public void BtnPlay_Click(object sender,EventArgs e)
     2 {
     3 
     4     IMediaFactory factory = null;
     5 
     6     switch (cbbMediaType.SelectItem.ToString().ToLower())
     7 
     8     {        
     9 
    10         case ("mp3"):
    11 
    12              factory = new MP3Factory();
    13 
    14              break;
    15 
    16               //其他类型略;
    17 
    18         case ("rm"):
    19 
    20              factory = new RMFactory();
    21 
    22              break;   
    23 
    24         //其他类型略;
    25 
    26     }
    27 
    28     IMedia media = factory.CreateMedia();
    29 
    30     media.Play();
    31 
    32 }
    33 
    34 

     

     

    如果你在最开始没有体会到IMedia接口的好处,在这里你应该已经明白了。我们在工厂模式中用到了该接口;而在客户端程序中,仍然要使用该接口。使用接口有什么好处?那就是你的主程序可以在没有具体业务类的时候,同样可以编译通过。因此,即使你增加了新的业务,你的客户端程序是不用改动的。

    不过,这样写客户端代码还是不够理想的,依然不够灵活,在判断具体创建哪个工厂的时候,仍需条件判断。现在看来,如果执行者没有完全和具体类分开,一旦更改了具体类的业务,例如增加了新的工厂类,仍然需要更改客户端程序代码。

    我们可以通过反射技术、条件外置很好地做到客户端的灵活性。

    条件外置来实现,即通过应用程序的配置文件来实现。我们可以把每种媒体文件类的类型信息放在配置文件中,然后根据配置文件来选择创建具体的对象。并且,这种创建对象的方法将使用反射技术来完成。首先,创建配置文件:

     

    <appSettings>

    <add key="mp3" value="MediaLibrary.MP3Factory" />

    <add key="wav" value=" MediaLibrary.WAVFactory" />

    <add key="rm" value=" MediaLibrary.RMFactory" />

    <add key="mpeg" value=" MediaLibrary.MPEGFactory" />

    </appSettings>

     

    然后,在客户端程序代码中,自定义一个初始化方法如:InitMediaType(),读取配置文件的所有key值,填充cbbMediaType组合框控件中:

     

    代码
    private void InitMediaType()
    {

        cbbMediaType.Items.Clear();

        
    foreach (string key in ConfigurationSettings.AppSettings.AllKeys)
       {

           cbbMediaType.Item.Add(key);

       }

       cbbMediaType.SelectedIndex 
    = 0;

    }

     

     

    最后,更改客户端程序的Play按钮单击事件:

     

    代码
    Public void BtnPlay_Click(object sender,EventArgs e)
    {

        
    string mediaType = cbbMediaType.SelectItem.ToString().ToLower();

        
    string factoryDllName = ConfigurationSettings.AppSettings[mediaType].ToString();

        
    //MediaLibray为引用的媒体文件及工厂的程序集;

        IMediaFactory factory 
    = (IMediaFactory)Activator.CreateInstance(“MediaLibrary”,

        factoryDllName).Unwrap();

        IMedia media 
    = factory.CreateMedia();

        media.Play();

    }

     

     

    这样可以很好地体现了软件工程的三个主要目标:重用性、灵活性和扩展性。

    设想一下,如果我们要增加某种媒体文件的播放功能,如AVI文件。那么,我们只需要在原来的业务程序集中创建AVI类,继承于VideoMedia类。另外在工厂业务中创建AVIFactory类,并实现IMediaFactory接口。假设这个新的工厂类型为MediaLiabrary.AVIFactory,则在配置文件中添加如下一行:

    <add key="AVI" value="MediaLiabrary.AVIFactory" />

    而客户端程序呢?根本不需要做任何改变,甚至不用重新编译,程序就能自如地运行!

    另:本文发布后有热心的朋友提出,如果提供UML图,那就更完美了,谢谢这位朋友的提醒。UML图现补充如下:

    展开全文
  • 1、构造函数 构造函数:可以看成一种用来创建或实例化对象的方法。构造函数唯一的用途是:使用一种已知的...对对象的默认状态无法满足类设计的要求时。 当非默认构造函数更易使用时。(3)构造函数重载 注意,构造函数

    1、构造函数
    构造函数:可以看成一种用来创建或实例化对象的方法。构造函数唯一的用途是:使用一种已知的状态来实例化对象。

    (1)默认构造函数
    创建默认构造函数,总是采用与类相同的名称。
    默认构造函数将对象放在一个已知状态中,其中值类型的值为0,引用变量的值为null。

    (2)非默认构造函数
    对对象的默认状态无法满足类设计的要求时。
    当非默认构造函数更易使用时。

    (3)构造函数重载
    注意,构造函数重载应该总是包含对象的默认状态。
    可以从参数化构造函数中调用默认构造函数。不能再重载构造函数的函数体中调用默认构造函数,这是不被编译器所承认的。
    需要用到冒号运算符‘:’即”从……继承“。

    public clsDates(int yr):this();

    如上,是实现重载构造函数包含默认构造函数功能的正确方式。(构造函数链)

    始终调用默认构造函数

    2、属性方法

    C#提供一种被称为属性方法的程序结构,用来访问在类中定义的私有方法。类属性方法用getter和setter构建。这些方法是用于类属性的特殊方法。(同C++程序设计)
    代码结构:

    AccessSpecifier ReturnDataType PropertyName
    {
    get
    {
    return PropertyMember;
    }
    
    set
    {
    PropertyMember=value;
    }
    
    }

    使用属性方法的原则:
    所有属性方法都使用public访问说明符。(作为用户API的一部分)
    属性方法的返回类型必须与该属性的数据类型相一致。
    注意属性方法不同于其他任何类型方法。因为属性方法的名称后面没有跟着一堆圆括号。
    常见的编程惯例是:使属性方法的名称和属性的名称相同,但是将属性名称的首字母大写。
    关键字get用来标记属性方法中检索某个类属性成员的值的代码块。(人们通常把get语句块称之为getter)
    关键字set用来标记属性方法内向类属性成员赋值的代码块。set语句应该是外部世界用来修改类的私有成员的唯一方式。(也可以使用General类的修改方式)
    value关键字是一个隐式变量,作为set语法的一部分,可以讲value看做一种ghost变量。用来存放要赋予属性成员中的数据。value的数据总是与属性方法的返回类型说明符一致。

    各个属性方法的工作方式
    要读取当前存储在clsDate对象中的month的值,下面的 代码片段说明了访问的方法:

    int myMonth;
    clsDates myDate=new clsDates();
    myMonth=myDate.Month;

    翻译:当month当前位于myDate对象中时,检索它的值。并且将改制赋予给myMonth。
    为了使得赋值和检索更加的智能,我们编译了month的set和get对象:

    public int Month
    {
    get{
    if(month==0)
    {
    return current.month;
    }
    else
    {
    return month;
    }
    }
    set
    {
    if(val>0&&val<13)
    {
    month=val;
    }
    }
    }

    set方法的工作方式:
    用下面的代码进行说明:

    int myMonth=12;
    clsDate myDate=new clsDate();
    myDate.Month=myMonth;

    VS编译器能够根据上下文来判断程序员的意图。原则如下 :
    如果属性方法名出线在赋值运算符右侧则执行get语句。
    如果属性方法名出现在赋值运算符左侧则执行set语句

    3、属性方法中错误对策
    问题:假值可能会进入属性方法。
    解决:(1)利用messagebox对象来向用户显示一条消息,表示属性中读到了一条坏值。(但是一种通用的风格约定:属性方法不适用MessageBox对象)
    (2)充分利用set语句块。set的除了修改类与方法关联属性的值,还可以让状态标志作为类的一个属性成员。
    如下对错误检测的代码:

    private int erroStatus=0;
    public int getErrorStatus
    {
    get
    {
    return errorStatus;
    }
    }
    

    注意:这种属性方法没有set语句块,他是只读属性方法,应为该方法是只读的,所以用户无法修改。
    然后在其他属性中对error进行检测:

    public int Month
    {
    get{
    if(month==0)
    {
    return current.month;
    }
    else
    {
    return month;
    }
    }
    set
    {
    if(val>0&&val<13)
    {
    month=val;
    }
    else
    {
    errorStatus=1;l0p
    }
    }
    }

    如上,在设置相关set方法之后,可以在后台向frmMain类中添加如下代码:

    myDate.Month=myMonth;
    if(myDate.getErrorStatus==1)
    {
    MssageBox.show("XXX","XXX");
    return;
    }

    方法内聚和耦合
    属性方法用来读写与类中属性关联的值,因为属性方法的访问说明符是public,所以他们构成了类访问界面的重要部分。
    Helper方法不属于API的一部分,用来减轻编写类的程序员的编码负担。其一般用途是进行有效性验证。
    General方法常用来从属性数据中衍生新的数据。

    内聚
    对于General方法,重点是要注意每个方法都是用于一个特定的严密定义的任务。这个属于是指:一至两个语句描述方法的作用的能力。(如果需要用多余两个语句来定义某个方法的作用,那么这个方法可能就不是内聚方法)
    极尽全力去清晰整洁简短代码,使得代码着眼于单个任务。

    耦合
    耦合是指程序中两个数据元素之间的依赖程度。方法耦合是指修改一个方法中的代码而不强制修改另一个方法中的代码的能力。程序设计的目的应该是:使得方法之间具有零耦合!!!

    4、扑克牌类程序设计
    注:此程序尽可能的体现前文所描述的类设计思想。
    问题:此程序应该简单地在一个列表框对象中显示一副洗过的牌。还应该通知用户通过多少次洗牌循环才产生了一副洗过的牌。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    
        class clsCardDeck
        {
            private const int DECKSIZE = 52;
    
            private static string[] pips ={" ",
            "AS","2S","3S","4S","5S","6S","7S","8S","9S","TS","JS","QS","KS",
            "AH","2H","3H","4H","5H","6H","7H","8H","9H","TH","JH","QH","KH",
            "AD","2D","3D","4D","5D","6D","7D","8D","9D","TD","JD","QD","KD",
            "AC","2C","3C","4C","5C","6C","7C","8C","9C","TC","JC","QC","KC",      
                                         };
    
            private int nextCard;
            private int[] deck=new int [DECKSIZE+1];
            private int passCount;
    
            public clsCardDeck()
            {
                nextCard = 1;
            }
    
            public int DeckSize
            {
                get 
                {
                    return DECKSIZE;
                }
            }
    
            public int NextCard
            {
                get 
                {
                    return nextCard;
                }
                set
                {
                    if (value > 0 && value <= deck.Length)
                    {
                        nextCard = value;
                    }
                }
            }
    
            public int PassCount
            {
                get {
                    return passCount;
                }
            }
    
            public int ShuffleDeck()
            {
                int index;
                int val;
                Random rnd = new Random();
                passCount = 0;
                index = 1;
                Array.Clear(deck,0,deck.Length);
                while (index < deck.Length)
                {
                    val = rnd.Next(DECKSIZE) + 1;
                    if (deck[val] == 0)
                    {
                        deck[val] = index;
                        index++;
                    }
                    passCount++;
                }
                nextCard = 1;
                return passCount;
            }
    
            public string getOneCard(int index) 
            {
                if (index > 0 && index <= deck.Length && nextCard < deck.Length)
                {
                    nextCard++;
                    return pips[index];
                }
                else
                {
                    return " ";
                }
            }
    
            public string getCardPip(int index)
            {
                if (index > 0 && index <= DECKSIZE)
                {
                     return pips[deck[index]];
                }
                else
                {
                    return " ";
                }
            }
        }
    
    

    注:新建类会吧自定义封装在namespace中,我们应该把其去掉。从而方便调用类对象。
    类属性的表述
    符号常量DECKSIZE:一副牌的张数。
    pips:存放牌的非图形表示。static存储的原因是数组的内容可以共享。
    nextCard:牌的索引。
    deck:表示这幅扑克牌。
    passCount:存储洗牌的次数。
    在类的构造函数中把nextcard初始化为1的原因是,使得第一张牌被作为deck【1】来对待。

    类方法的表述
    shuffledeck()方法
    I.将牌计数器变量初始化为0;
    II.产生一个1~DECKSIZE的范围内的随机数。
    III.用产生的随机数作为deck数组的随机索引。
    IV.如果deck数组的带索引元素的值为0,就是没有被赋值,因此可以安全地将该索引号赋予数组的该元素,并且将纸牌计数器递增1。
    V.如果deck数组的带索引元素不是0,说明该数组的那个元素已经被使用。
    VI.如果小于DECKSIZE,则重复步骤2。
    (同时这个方法的设计避免了对产生随机数是否重复的判断,非常机智。 )

    下面来写出关于frmMain类的代码:

    using System;
    using System.Windows.Forms;
    public class frmMain : Form
    {
        private Button btnShuffle;
        private Button btnClear;
        private Button btnClose;
        private ListBox lstDeck;
        private Label lblPassCounter;
        #region Windows code
        private void InitializeComponent()
        {
    
        }
        #endregion
        //#region是C# 预处理器指令。 
        //#region 使您可以在使用 Visual Studio
        //代码编辑器的大纲显示功能时指定可展开或折叠的代码块。
        public frmMain()
        {
            InitializeComponent();
        }
    
        public static void Main()
        {
            frmMain main = new frmMain();
            Application.Run(main);
        }
    
        private void btnShuffle_Click(object sender, EventArgs e)
        {
            int j;
            int cardIndex;
            int deckSize;
            int passes;
            string buff;
            string temp;
            clsCardDeck myDeck = new clsCardDeck();
    
            passes = myDeck.ShuffleDeck();
            lblPassCounter.Text="It took "+passes.ToString()+" passes to shuffle the deck";
    
            deckSize = myDeck.DeckSize;
    
            for(cardIndex=1;cardIndex<deckSize+1)
            {
                buff=" ";
                for(j=0;j<13;j++)
                {
                temp=myDeck.getOneCard(cardIndex);
                if(temp.Length==0)
                {
                MessageBox.Show("Errror reading deck.","Proceeding error");
                   return ;
                }
                    buff+=temp+" ";
                    cardIndex++;
                }
                lstDeck.Items.Add(buff);
            }
            lstDeck.Items.Add(" ");
        }
    
        private void btnClose_Click(object sender, EventArgs e)
        {
            Close();
        }
    
        private void btnClear_Click(object sender, EventArgs e)
        {
            lstDeck.Items.Clear();
        }
    }

    和往常一样,所有的动作都发生在btnShuffle中。
    两个for循环的嵌套,可以实现每行13张的格式。

    5、设计使用clsCardDeck的纸牌游戏
    游戏机制:首先是一个处理器(计算机)。100美元下注,然后处理器发出两张,面朝上的牌。一场游戏可以不下注,可以将拥有的所有财产都压上去。如果下了注,处理器就发下一张牌。如果那张牌位于其他两张牌形成的范围内,您就赢得了等于您赌注的金额。赢得的金额自动添加到您的余额中。如果第三张牌在前两张牌的范围之外,那么处理器就赢了。会从您的余额中减去您的赌注金额。如果三张牌等于前两张牌中的任何一张也是处理器赢。

    (1)设计考虑
    游戏是通过规则管理动作。<客户机-服务器体系>服务器是clsCardDeck。一般来讲,游戏的三个类:
    客户机–规则–服务器。以这种机制运行着。(三层体系结构)
    规则代码:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    
        class clsInBetween
        {
            const int TIE = 0;
            const int PLAYERWINS = 1;
            const int DEALERWINS = 2;
    
            private int balance;
            private int wager;
            private int lowCard;
            private int lowCardIndex;
            private int hiCard;
            private int hiCardIndex;
            private int dealtCard;
            private int dealCardIndex;
    
            private clsCardDeck myDeck;
             public clsInBetween()
            { 
            balance=100;
            wager=10;
            myDeck=new clsCardDeck();
            }
            public int Balance{
            get{
            return balance;
            }
                set 
                {
                if(value>=0)
                    {
                balance =value;
                }
                }
            }
            public int Wager
            {
            get{return wager;}
            set
            {
            if(value>0)
            {
            wager=value;
            }
            }
    }
    
    
            private void SetCards(string[] hand)
            {
            int temp;
                hand[0]=myDeck.getCardPip(lowCardIndex);
                hand[1]=myDeck.getCardPip(hiCardIndex);
                hand[2]=myDeck.getCardPip(dealCardIndex);
    
                if(lowCard==hiCard||lowCard<hiCard)
                {
                hand[0]=myDeck.getCardPip(lowCardIndex);
                    hand[1]=myDeck.getCardPip(hiCardIndex);
                }
                else
                {
                temp=hiCard;
                    hiCard=lowCard;
                    lowCard=temp;
    
                    temp=hiCardIndex;
                    hiCardIndex=lowCardIndex;
                    lowCardIndex=temp;
                    hand[0]=myDeck.getCardPip(lowCardIndex);
                    hand[1]=myDeck.getCardPip(hiCardIndex);
    
                }
            }
    
            private void SetWinnerAndPosition(ref int outCome,ref int position)
            {
            if(dealtCard==lowCard)
            {
            outCome=DEALERWINS;
                position=2;
                return;
            }
            if(dealtCard<lowCard)
            {
            outCome=DEALERWINS;
                position=1;
                return;
            }
                if(dealtCard>lowCard&&dealtCard<hiCard)
                {
                outCome=PLAYERWINS;
                    position=3;
                    return;
                }
                if(dealtCard==hiCard)
                {
                outCome=DEALERWINS;
                    position=4;
                    return;
                }
                if(dealtCard>hiCard)
                {
                outCome=DEALERWINS;
                    position=5;
                    return;
                }
            }
    
            public void getFirstCard()
            {
            lowCardIndex=myDeck.getOneCard();
                lowCard=lowCardIndex%13;
                if(lowCard==0)
                {
                lowCard=13;
                }
            }
    
            public void getSecondCard()
            {
            hiCardIndex=myDeck.getOneCard();
                hiCard=hiCardIndex%13;
                if(hiCard==0)
                {
                hiCard=13;
                }
            }
    
            public void getDealtCard()
            {
            dealCardIndex=myDeck.getOneCard();
                dealtCard=dealCardIndex%13;
                if(dealtCard==0)
                {
                dealtCard=13;
                }
            }
    
            public void Shuffle()
            {
            myDeck.ShuffleDeck();
            }
    
            public int getCardLeft()
            {
            return myDeck.getCardsLeftInDeck();
            }
    
            public void DealHand(string[] hand,ref int outCome,ref int position)
            {
            getFirstCard();
                getSecondCard();
                getDealtCard();
                SetCards(hand);
    
                SetWinnerAndPosition(ref outCome,ref position);
            }
    
        }
    
    

    服务器代码:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    
    class clsCardDeck
    {
        private const int DECKSIZE = 52;
    
        private static string[] pips ={" ",
            "AS","2S","3S","4S","5S","6S","7S","8S","9S","TS","JS","QS","KS",
            "AH","2H","3H","4H","5H","6H","7H","8H","9H","TH","JH","QH","KH",
            "AD","2D","3D","4D","5D","6D","7D","8D","9D","TD","JD","QD","KD",
            "AC","2C","3C","4C","5C","6C","7C","8C","9C","TC","JC","QC","KC",      
                                         };
    
        private int nextCard;
        private int[] deck = new int[DECKSIZE + 1];
        private int passCount;
    
        public clsCardDeck()
        {
            nextCard = 1;
        }
    
        public int DeckSize
        {
            get
            {
                return DECKSIZE;
            }
        }
    
        public int NextCard
        {
            get
            {
                return nextCard;
            }
            set
            {
                if (value > 0 && value <= deck.Length)
                {
                    nextCard = value;
                }
            }
        }
    
        public int PassCount
        {
            get
            {
                return passCount;
            }
        }
    
        public int ShuffleDeck()
        {
            int index;
            int val;
            Random rnd = new Random();
            passCount = 0;
            index = 1;
            Array.Clear(deck, 0, deck.Length);
            while (index < deck.Length)
            {
                val = rnd.Next(DECKSIZE) + 1;
                if (deck[val] == 0)
                {
                    deck[val] = index;
                    index++;
                }
                passCount++;
            }
            nextCard = 1;
            return passCount;
        }
    
        public int getOneCard()
        {
            nextCard++;
            if (nextCard < deck.Length)
            {
    
                return deck[nextCard];
            }
            else
            {
                return 0;
            }
        }
    
        public int getCardsLeftInDeck()
        {
            return DECKSIZE - nextCard;
        }
    
        public string getCardPip(int index)
        {
            if (index > 0 && index <= DECKSIZE)
            {
                return pips[index];
            }
            else
            {
                return " ";
            }
        }
    }

    主类代码:

    using System;
    using System.Windows.Forms;
    public class frmMain : Form
    {
        const int TIE = 0;
        const int PLAYERWINS = 1;
        const int DEALERWINS = 2;
    
        int betResault;
        int wager;
        int balance;
        int position;
    
        clsInBetween myRule = new clsInBetween();
        string[] cards = new string[3];
    
        private TextBox txtLow;
        private TextBox txtHi;
        private GroupBox groupBox1;
        private Label lblLow;
        private Label lblHi;
        private Button btnDeal;
        private Button btnBet;
        private Label label1;
        private Label label3;
        private Button btnReset;
        private Button btnClose;
        private TextBox txtBalance;
        private Label label4;
        private Label lblOutcome;
        private TextBox txtWager;
        #region Windows code
        private void InitializeComponent()
        {
    
    
        }
        #endregion
        //#region是C# 预处理器指令。 
        //#region 使您可以在使用 Visual Studio
        //代码编辑器的大纲显示功能时指定可展开或折叠的代码块。
        public frmMain()
        {
            bool flag;
            InitializeComponent();
    
            txtBalance.Text = myRule.Balance.ToString();
            txtWager.Text = myRule.Wager.ToString();
            flag = int.TryParse(txtBalance.Text,out balance);
            flag = int.TryParse(txtWager.Text,out wager);
    
            myRule.Shuffle();
        }
    
        public static void Main()
        {
            frmMain main = new frmMain();
            Application.Run(main);
        }
    
        private void textBox1_TextChanged(object sender, EventArgs e)
        {
    
        }
    
        private void textBox2_TextChanged(object sender, EventArgs e)
        {
    
        }
    
        private void btnDeal_Click(object sender, EventArgs e)
        {
            int retval;
    
            ClearRanges();
    
            retval = myRule.Balance;
            if (retval == 0)
            {
                MessageBox.Show("You are broke,game over");
                return;
            }
            retval = myRule.getCardLeft();
            if (retval < 3)
            {
                lblOutcome.Text = "Deck was shuffled";
                myRule.Shuffle();
            }
            myRule.DealHand(cards,ref betResault,ref position);
            ShowHiLow();
    
    
        }
    
        private void ShowHiLow()
        {
            txtHi.Text = cards[0];
            txtLow.Text = cards[1];
        }
    
        private void ClearRanges()
        {
            lblHi.Text = " ";
            lblLow.Text = " "; 
        }
    
        private void btnClose_Click(object sender, EventArgs e)
        {
            Close();
        }
    
        private void btnReset_Click(object sender, EventArgs e)
        {
            myRule.Balance = 100;
            txtBalance.Text = "100";
            txtWager.Text = "10";
            ClearRanges();
        }
    
        private void btnBet_Click(object sender, EventArgs e)
        {
            bool flag = int.TryParse(txtWager.Text,out wager);
            if (flag == false)
            {
                MessageBox.Show("Dollar bets only.re-enter","Input error");
                txtWager.Focus();
                return;
            }
            switch (betResault)
            { 
                case TIE:
                    lblOutcome.Text = "Tie dealer wins";
                    myRule.Balance -= wager;
                    break;
                case PLAYERWINS:
                    lblOutcome.Text = "You win";
                    myRule.Balance += wager;
                    break;
                case DEALERWINS:
                    lblOutcome.Text = "Sorry,you lose.";
                    myRule.Balance -= wager;
                    break;
            }
            txtBalance.Text = myRule.Balance.ToString();
    
        }
    
    
    
    
    }

    其中的ref关键字,是将变量中的lvalue发给方法,从而永久地修改变量的值。

    展开全文
  • C#分层 C#分层思想 C#分层实例 C#分层项目 C#分层详解
  • 通过本课的学习,使学生掌握C#语言的语法知识,理解和掌握面向对象程序设计思想和方法,能熟练使用Visual Studio集成开发环境编写、调试和测试控制台应用软件、Winform应用软件;运用ADO.NET开发数据库应用程序,...
  • 在UML的学习过程中,画类图、对象图的时候面向对象的思想就已经用到了具体的学习中,而C#的学习过程中我们接着深入来学习这种思想,只不过这次是把OOP的思想用到了实际的代码编程之中。  我们知道面向对象的四个...

       纵观MicroSoft公司的几种主流语言,C是面向过程,VB既有面向过程也有面向对象的特性,而C#则是纯粹的面向对象的编程语言。在UML的学习过程中,画类图、对象图的时候面向对象的思想就已经用到了具体的学习中,而C#的学习过程中我们接着深入来学习这种思想,只不过这次是把OOP的思想用到了实际的代码编程之中。

       我们知道面向对象的四个基本原则:抽象、封装、模块化、层次性;面向对象思想的编程特性:继承性、封装性、多态性。通过楚老师的视频,对面向对象的四个基本原则和编程特性都有了新的认识,这篇博客就先对其中的部分特性进行分析。

    一、静态变量与非静态变量

       在上一篇博客中,对C#基础的分析是以“数据”引出来的,数据中变量在程序设计中占有一席之地,那我们就从静态变量和非静态变量两个角度对它进行分析。

    using system;
    using  system.Collections.Generic; 
    using   System.text
    namespace  ConsoleApplication1
    {
         class  TestStatic
         {
               public static void main()
              {
                    int k=AccessTest.x;    //此处直接使用,如果不是静态变量则写成 AccessTest myOk = newAccessTest();
                    Console.WriteLine(k);
                    AccessTest myOk=new AccessTest();
                    Cosole.WriteLine(myOK.y);  //通过实例化才能访问y
              }
         }
         public class AccessTest
         {
             public static int x=12;
             public int y=12;
          }
    }
    

       由例子可知,在AccessTest类中定义了两个变量x和y,其中x为静态static,y为非静态,在TestStatic类的Main函数如果要分别对AccessTest类中的x和y进行访问,则对于x可以直接使用,而对于y需要首先实例化AccessTest类,然后再由该实例去调用其中的y。也就是说静态变量中的成员可以通过类名直接进行访问,而实例成员(非静态变量)必须通过建立对象的引用来访问(此处区别对象和对象引用,对象引用是一个地址而不是一个值)。我想如果x和y都定义成了非静态变量的话,那样代码量会大增的。

       通过学习,对static变量有了自己的认识,但是理解的程度仅限于此,至于在代码中何处该用静态变量,还有待于在以后的学习中去探索。

    二、方法

      所谓的方法,就是对象的行为,在之前学习VB的时候对象有属性、事件和方法,而在真正面向对象的语言中没有事件的概念,或者说事件就是属于方法中的。

      方法的句法如下:

      [访问修饰符] 返回值类型  <方法名> ([参数列表])//不需要返回值时用void

       {

        //方法主体

       }

       正如上面介绍的静态变量和非静态变量那样,调用静态的方法同样无需进行实例化,而非静态的方法需要进行类的实例化之后进行调用该方法。

       方法可以被重复调用即“方法重载”在C#中是一个非常重要的定义,视频中老师将其分为“具有不同数量参数的方法重载”和“对不同数据执行相似功能的方法重载”,如下:

    Using System;
    Namespace chognzai
     {
       Class Class1
       {
          Public int a,b;
          Public double x,y;
          Public int fun(int a,int b)  //方法名称相同,参数不同
          {
             Return a+b;
          }
          Public double fun(double x,double y) //方法名称相同,参数不同
          {
             Return a+b;
          }
          Static void Main(string[] args)
          {
             Class1 sd=new Class1();
             Sd.a=156;
             Sd.b=32;
             Sd.x=23.56;
             Sd.y=56.49;
             Console.WriteLine(“{0}+{1}={2}”,sd.a,sd.b,sd.fun(sd.a,sd.b));
             Console.writeLine(“{0}+{1}={2}”,sd.x,sd.y,sd.fun(sd.x,sd.y));
             Console.ReadLine();
          }
       }
     }
    

        该例子中成员函数fun()被重载,属于“对不同数据执行相似功能的方法重载”,再次验证了方法是可以多次使用的这个观点。

    三、属性引发的封装性的体现

      先看个小例子,这是在视频中非常经典的那个交电话费的例子。

    namespace Customer
    {
         public enum TypeofCall  
         {
             callToMobile,CallTOLandLine
          }
         class Program
          {
              static void Main(string[] args)
               {
                  Customer tom=new Customer();
                  tom.CustomerName="楚广明";  //设置属性的值
                  tom.RecordCall(TypeofCall.callToMobile,20);
                  tom.RecordCall(TypeofCall.callToLandline,40);
                  Console.WriteLine("{0}有{1}电话费要交",tom.CustomerName,tom.customerBalance)  //读取属性的值
               }
          }
             public class Customer
            {
              private string name;
              private decimal balance;
              public string CustomerName  //*****属性****//
             {
                     get( return name;)
                     set (  name=value;)
               }
               public decimal CustomerBalance //****属性***//
                {
                    get ( return balance;)   //只能读取余额,而不能修改
                }  
                public void RecordPayment(decimal amountpaid) //***方法*** //
                {
                    balance-=amountpaid;
                }
                public void RecordCall(TypeofCall callType,uint nMinute) //***方法*** //
                 {
                      swistch(callType)
                       {
                           case TypeofCall.CallToLandline:
                                 balance+=(0.02M*nMinutes); 
                                  break;
                            case TypeofCall.callToMobile:
                                  balance+=(0.3M*nMinutes);
                              default:
                                    break;
                        }
                 }
              }
    }
    

        例子有点长,其中仅仅对属性进行分析,就像:

               public string CustomerName  //*****属性****//

               {

                    get( return name;)

                    set (  name=value;)

               }

              public decimal CustomerBalance //****属性***//

                {

                   get ( return balance;)   //只能读取余额,而不能修改

               } 

       这两个属性是这个例子中的Customer类实例化后tom对象的两个属性,很明显属性区别于方法就像当初学习VB一样,一个说明的是属性,另一个说的是动作。从这个例子中还能体现出面向对象封装性的思想,就像CustomerBalance属性那样,仅仅为读取属性,而没有设置属性的代码,这样我们在主程序中就不能去修改,从而保护了隐私。也就是说通过属性,将类的成员变量隔离,通过赋值、取值实现间隐藏封装的功能,这也可以看做是一种权限的限制。

        通过这个小例子,OOP的封装性也就是通过其他间接的手段去操作类的成员变量,不要让别人知道你的成员变量中包含着什么,当然这里所谓的间接手段可以是上面所提到的属性,也可以是方法,前提是该方法的访问修饰符是public,通过该方法调用相同类中访问修饰符为private的成员变量或者成员方法,从而实现封装,在C#视频中有个“访问密码的最短长度(private)”的例子就是通过这个方法实现的。

    总结:

        在C#中面向对象的思想吗,目前仅仅是了解,需要急切去做的就是到《设计模式》的时候多去敲例子,多去回顾现在学的这些东西,带着问题去学新知识,从而实现“温故而知新”的效果。

        在接下来的面向对象总结中将对“继承、多态”做浅析,期待。






    展开全文
  • 为什么有这种想法是因为之前跟一些同事交流技术的时候会发现很多设计思维被固化了,比如之前我在做客户端框架开发的时候会去设计一些关于Validator、DTO Transfer等常用的Common function,但是发现在讨论某一些技术...
  • C#设计模式》前言

    2013-07-08 20:50:14
    昨日收到清华大学出版社寄来的《C#设计模式》样书,本书在之前几本Java版的设计模式书籍的基础上又补充了不少内容,并配有大量的实例和习题,还赠送设计模式综合测试题两套,,希望能够给C#开发人员和一些高校师生...
  • C#设计串口助手

    2018-09-16 23:34:39
    考研期间,利用晚上一点时间,照葫芦画瓢,练习使用c#快速开发一个简单的串口小助手。 这种前后端分离的设计方法,大大提高了开发速度,比Python的wxPython写软件方便很多。 但是使用c#写软件在群里问问题的时候,...
  • C#版本的23种设计模式已经写完了,现在也到了一个该总结的时候了。说起设计模式,我的话就比较多了。刚开始写代码的时候,有需求就写代码来解决需求,如果有新的需求,或者需求变了,我就想当然的修改自己的代码来...
  • 软件开发设计思想

    2015-12-16 15:23:46
     瀑布模型核心思想是按工序将问题化简,将功能的实现与设计分开,便于分工协作,即采用结构化的分析与设计方法将逻辑实现与物理实现分开。将软件生命周期划分为制定计划、需求分析、软件设计、程序编写、软件测试和...
  • C#设计模式之单例模式
  • 俗话说:“龙生龙凤生凤,老鼠的儿子会打洞”,可见在生活中,这种面向对象的思想(既“继承”)已经深入人心,这篇博客,就主要针对“继承”这个概念做稍微深入一点的总结。 一、 继承的实现  所谓的继承:即派生...
  • 最近总结了c#视频里的内容,算是初步熟悉面向对象编程里的一个小小的印记吧!一接触面向对象,感觉这种思想很简单,就是类似分类汇总的过程,将现实生活中的理论应用于代码!  面向对象思想是简单高效的,但是并不...
  • C#设计模式之策略模式
  • C#这个东西听说时还是当时自己学C语言的时候,那时甚至都不知道什么叫C++,什么叫Java。更别说是面向对象的思想了。之前学到的都是面向过程的技术,实现一个功能要一点一点去理清逻辑,然后才能勉强的写出来。后来学...
  • 为了更好的理解设计思想,结合一个尽可能简洁的实例来说明OOD、设计模式及重构。通过下面的代码,详细地阐述面向对象设计思想。 一、传统过程化设计思想假定我们要设计一个媒体播放器(只从软件设计的角度,不涉及...
  • 简单的C#五子棋设计

    2019-07-13 10:56:46
    1 系统设计思路 1.1 总体设计思路 棋盘设计为10×10,初始状态光标在棋盘 的央,黑棋玩家先走轮流落,当一方连成五子或下满棋盘时游戏结束(连成五子的一方获胜,下满棋为和棋)。当游戏一方胜利显示胜利信息。从...
  • 今天我们开始第一次课,我们本门课的题目叫“C#面向对象程序设计语言”。那么什么是“C#”,什么又是“面向对象”呢?,程序设计语言的概念,我们在ITFC中已经学习过了(程序:为了让计算机执行某些操作或解决某个...
  • C#基础教程-c#实例教程,适合初学者。 第一章 C#语言基础 本章介绍C#语言的基础知识,希望具有C语言的读者能够基本掌握C#语言,并以此为基础,能够进一步学习用C#语言编写window应用程序和Web应用程序。当然仅靠一...
1 2 3 4 5 ... 20
收藏数 50,118
精华内容 20,047
热门标签
关键字:

c#设计思想