(13)C#传智:访问修饰符,简单工厂模式,序列化与反序列化,部分类,密封类,接口(第13天)

内容超级多,慢慢来。。。

    
    深入BinaryFormatter
    


一、访问修饰符


    public: 公共的,公开的
    private:私有的,只能在当前类的内部访问
    protected:受保持的,只能在当前类的内部和该类的子类中访问。
    internal:只能在当前程序集(项目)中访问。
              同一项目中public与internal权限一样。
    protected internal:从当前程序集或派生自包含类的类型访问受保护的内部成员。
    
    internal的感观认识:
        在同一个解决方案中,
        a)在Test项目中添加两个类:
            internal class Person{}
            public class Student{}
        b)再新建一个项目Luck,若想从Luck项目中访问Test项目中的Person与Student类,
            首先在Luck项目(在右侧解决方案中右击Luck项目目录中的“引用”),添加
                引用,弹出的窗体中选择Test项目(窗体左侧解决方案中的“项目”中)
            第二步,在Luck项目的最上面添加命名空间:using Test;
            第三步,在Luck内部就可以直接使用上面Student进行声明赋值,注意的是,
                鼠标指向上面声明代码时,会提示是Test.Student,说明是Test项目的.
                
            但是此时用Person p=new Person();时会发生错误,说明不能访问到项目
            Test中的internal Person。因为internal只能在项目内使用,跨项目会
            提示错误。这就是public与internal的区别。
            
    protected internal感观认识:
        实际上是两种方式都可以用,比较灵活。
        既可是internal即项目内访问,也可以protected在本类及子类中访问(可能跨项目)
        实例:在同一解决项目中有两个项目Test与Luck,其中Test中引用Luck。
        Luck项目:

        namespace Luck
        {
            public class BaseClass
            { protected internal int myValue = 0; }

            internal class TestAccess
            {
                private void Access()
                {
                    var baseObject = new BaseClass();
                    //internal同项目,所以下句可以访问
                    baseObject.myValue = 5;
                }
            }
        }


        Test项目,主程序

        using System;
        using Luck;
        namespace Test
        {
            internal class Program : BaseClass //继承
            {
                private static void Main(string[] args)
                {
                    //引用后,可访问Luck项目中的public BaseClass
                    var b = new BaseClass();
                    //Program继承于BaseClass,可访问父类的proctected myValue
                    var InheritObject = new Program();
                    InheritObject.myValue = 10;//因继承而能访问
                    b.myValue = 10;//错误,跨项目只能用继承才能访问
                    Console.ReadKey();
                }
            }
        } 

   
    
    internal与protected的访问权限谁大?
        internal仅限于本项目内,出了该项目就不能访问。
        protected是本类及子类,可能没出本项目,也可能出了本项目,只要该类或本类
                 都可访问。
        所以两者各有优势,没有谁大谁小之说。
    
    1)能够修饰类的访问修饰符只有两个:public,internal
        默认的修改是internal,所以一般需人为添加为public。    
    
    2)可访问性不一性
        子类的访问权限不能高于父类的访问权限。
        例:internal class Person{}
            public class Student:Person{} 
        错误,子类权限大于父类本想限制在本项目内的权限,会暴露父类成员。

    
二、简单工厂设计模式


    设计模式:设计这个项目的一种方式。程序的巧妙写法,不是算法,是表达方式。
              如同文章写法:总分结构、分总结构、总分总结构、倒叙结构...等等
    程序表达一样,有很多通用的表达方法,把一些相同的表达方法归纳总结成一种,这种就
    形成一种设计模式。同样另一些相同的归纳成另一种设计模式。以此类推...
            
    《C#设计模式》,23种,从头到尾敲一篇。
    设计模式是软件开必过程中经验的积累,特定问题的经过实践的特定方法。
     先看后面实例后,再看前面的文字。。。


    1、什么是工厂?
        1)客户不需要知道怎么做的, 但是不影响使用
        我们身边有很多工厂:酿酒的酒厂, 制衣的衣厂, 加工肉类的肉加工厂等等.这些工厂他
        们到底是怎么酿酒的? 怎么制衣的?怎么加工肉的? 我们并不知道, 也不需要知道. 不知
        道并不影响我们喝酒, 穿衣, 吃肉. 这就是工厂的特点之一.
        2)给你材料, 你去制造出我想要的东西, 至于怎么做, 我并不关心.
        比如肉加工厂---双汇. 牛肉进去出来牛肉火腿肠, 羊肉进去出来羊肉火腿肠, 猪肉进去出
        来猪肉火腿肠. 我不需要知道怎么加工的, 我只需要把材料扔进去, 然后对应的火腿肠就
        出来了. 这就是工厂的第二个特点。
    
    2、什么是设计模式?
        我们基本都知道设计模式有23种。
        设计模式不是语法, 而是一种巧妙的写法, 能够把程序变得更加灵活的写法.
        设计模式有三种: 创建型, 行为型, 结构型. 简单工厂设计模式属于创建型.
        
        但简单工厂设计模式不属于23种设计模式范围内, 属于23种设计模式中工厂设计模式
        里最简单的一种。
    
    3、什么是简单工厂模式?
        简单工厂设计模式, 又叫做静态工厂设计模式
        简单工厂设计模式提供一个创建对象实例的功能,而无需关心其具体实现,被创建实例
        的类型可以是接口、抽象类,也可以是具体的类。
        
    4、简单工厂设计模式的四个要素
        这个很重要, 这也是创建一个简单工厂的步骤:
        1)API接口: 创建一个API接口或抽象类
        2)Impl实现类: 一个或多个实现API接口/抽象类的类
        3)工厂: 定义一个工厂, 用来创建API接口类对象
        4)客户端: 用来调用工厂创建API接口或抽象类的客户端
        
    5、简单工厂创建过程:
        第一步: 定义API接口或抽象类, 并定义一个operate操作方法;
        第二步: 定义API的实现类, 每个实现类单独实现operate方法;
        第三步: 定义工厂类. 工厂类依赖API接口和API的实现类, 简单工厂设计模式是创建型的,
            通常是用来创建实体类. 因此我们定义一个create方法, 来创建实例对象,入参通常
            是一个指定的类型.
        第四步: 定义客户端. 客户端传入一个指定的类型给工厂, 工厂就会创建出对应的实现类.
    
    6、简单工厂设计模式例子
        用户需要指定品牌笔记本。
        工厂万能满足用户需要。
        两者之间,通过工厂来实现。通过特定品牌创建子类,返回通用的父类(子类转换而来)
        
        工厂实现的前提:做好接口或抽象类,并在子类中实现。
        
        用户需求笔记本:   用户需求笔记本<---->父类(子类品牌:Acer,Lenove,Dell...)
    只需提供给用户笔记本的父类,就可以随意定制其子类的笔记本。
    
    先定义工厂父类与各笔记本子类

    public abstract class NoteBook
    {
        public abstract void Say();
    }
    public class IBM : NoteBook
    {
        public override void Say()
        { Console.WriteLine("我是IBM笔记本"); }
    }

    public class Lenovo : NoteBook
    {
        public override void Say()
        { Console.WriteLine("我是联想笔记"); }
    }
    public class Dell : NoteBook
    {
        public override void Say()
        { Console.WriteLine("我是Dell笔记本"); }
    }


    
    再在主程序中,调用父类制作具体的子类笔记本。
 

    private static void Main(string[] args)
    {
        Console.WriteLine("请入输入要制作的品牌笔记本:");
        string brand = Console.ReadLine();
        NoteBook n = GetNB(brand);
        n.Say();
        Console.ReadKey();
    }


    //核心部分
 

    public static NoteBook GetNB(string brand)
    {
        NoteBook nb;
        switch (brand)
        {
            case "Dell": nb = new Dell(); break;
            case "Lenovo": nb = new Lenovo(); break;
            default: nb = new IBM(); break;
        }
        return nb;
    }


    
    
三、值类型与引用类型


    值类型:int,double,decimal,char,bool,enum,struct.(存储上)
    引用类型:string,数组,自定义类,集合,接口.(存储上)
    
    值传递与引用传递
    值类型在赋值的时候,传递是值的本身(复制了原来的一份)
    引用类型赋值时,只是传递了这个对象的引用(原对象未复制),即复制的是指针。
    
    ref:将值传递改变成引用传递.
    
    例:创建一个类

    public class Person
    {
        private string _name;
        public string Name { get => _name; set => _name = value; }
    }


    
    主函数中运行:

    private static void Main(string[] args)
    {
        int n1 = 10, n2 = n1;
        n2 = 20;
        Console.WriteLine("n1:{0},n2:{1}", n1, n2);//n1:10,n2:20
        
        Person p1 = new Person();
        p1.Name = "李四";
        Person p2 = p1;
        p2.Name = "张三";
        Console.WriteLine("p1:{0},p2:{1}", p1.Name, p2.Name);//p1:张三,p2:张三
        TestRef(p1);
        Console.WriteLine(p1.Name);//引用类型,在传参时一样指向同一地址
        
        string s1 = "王五";
        string s2 = s1;//两者一样
        s2 = "邓六";//因字符串不可变性,新赋值将开辟新的空间,地址发生更改,不再同一地址.
        Console.WriteLine("s1:{0},s2:{1}", s1, s2);//s1:王五,s2:邓六

        int n3 = 10;
        TestInt(n3); //值传递
        Console.WriteLine(n3);//10
        
        TestIntRef(ref n3); //引用传递
        Console.WriteLine(n3);//11
        Console.ReadKey();
    }

    public static void TestRef(Person p)
    { Person p3 = p; p3.Name = "王五"; }

    public static void TestInt(int n)
    { n++; }

    public static void TestIntRef(ref int n)
    { n++; }    

四、序列化与反序列化


    序列化:将对象转化为二进制.
    反序列化:将二进制转化为对象。
    作用:传输数据,因为只有二进制才能被传输。
      从A地将O对象传到B地:
      A地O对象序列化成二进制,B地接受二进制后,反序列化成O对象。最终O对象从A到达了B。
      
    序列化步骤:
    1)对象标记:[Serializable]
    2)使用FileStream流,用BinaryFormatter进行写入流为二进制.
    
    反序列化步骤
    1)使用FileStream流读取
    2)使用Deserialize反序列化,并强制转为对象类型。从而获得对象数据。
    
    例子:下面是序列化后保存到文件,反序列化:把文件读取后反序列到对象.

    internal class Program
    {
        private static void Main(string[] args)
        {
            Person p = new Person { Gender = 'M', Name = "Luck" };
            using (FileStream fsw = new FileStream(@"E:\1.txt", FileMode.OpenOrCreate, FileAccess.Write))
            {
                BinaryFormatter bf = new BinaryFormatter();//开始序列化
                bf.Serialize(fsw, p);//自动转p为二进制,用流fsw写入
            }
            Console.WriteLine("序列化完毕,去1.txt查看");

            //反序列化
            Person p2;
            using (FileStream fsr = new FileStream(@"E:\1.txt", FileMode.Open, FileAccess.Read))
            {
                BinaryFormatter bf = new BinaryFormatter();
                p2 = (Person)bf.Deserialize(fsr);//返回为object须转换类型
            }
            Console.WriteLine(p2.Name);
            Console.ReadKey();
        }
    }

    [Serializable]
    public class Person
    {
        private string _name;
        private char _gender;

        public string Name { get => _name; set => _name = value; }
        public char Gender { get => _gender; set => _gender = value; }
    }


    
五、部分类partial


    同一命名空间不能定义两个同名的类.
    
    但实际工作中,一个类很大,需要三个人同时来写这个类,因此出现部分类的概念。
    就是每个人都可以在同一命名空间下写同名的类,但前面加prtial,表明是这个类
    的一部分,三个人写的这个类都可以相互访问,因为这个同名部分的类本质是一个
    类,哪怕分别是private.
    
    当然不能有同名的字段;不能有同名的方法(除非重载
    
    例子:

    internal class Program
    {
        private static void Main(string[] args)
        {
            Student s = new Student { Id = 12, Name = "比尔" };//部分类汇总
            s.SayID();
            Console.ReadKey();
        }
    }

    public partial class Student//部分类
    {
        private string _name;

        public string Name { get => _name; set => _name = value; }

        public void Play()
        { }

        public void SayID()
        { Console.WriteLine(this.Id); }//可以使用下面部分类中的成员Id
    }

    public partial class Student//部分类
    {
        private int _id;

        public int Id { get => _id; set => _id = value; }

        //public void Play() { }//部分类的方法签名必须不一样
        public void Talk()
        { Console.WriteLine(this.Name); }//可以使用上面部分类中的成员Name
    }


    
六、密封类sealed


    密封者,不泄露,不继承。相当于断子绝孙,不再向下继承传递.
    
    在类前标记为sealed,即为密封类,表明不再向下继承。

    public sealed class Animal
    { }

    public class Tiger : Animal //错误父类已密封不得继承
    { }


    
    但是密封类可以从别人处继承而来。

    public class Life
    { }

    public sealed class Animal:Life//密封类可从别人处继承
    { }    


    
七、重写ToString()方法


    直接输出一个对象时,一般出现的就是ToString,显示的是对象的命名空间。
    所有类型都可以ToString,因为都继承于Object对象中的ToString()
    
    object中有三个虚方法:Equals(object obj)
                          GetHashCode()
                          ToString()
    例子: 

    internal class Program
    {
        private static void Main(string[] args)
        {
            Rabbit r = new Rabbit();
            Console.WriteLine(r.ToString());//Study Hard
            Console.ReadKey();
        }
    }

    public class Rabbit
    {
        public override string ToString()//重写
        { return "Study Hard"; }
    }

    
八、接口简介

    接口结束后,C#的基础就讲完了。
    
    1、普通属性与自动属性
          普通属性有字段与属性两者;自动属性只有属性简写,没有方法体
          但两者本质上经反编译后是相同的,普通属性不清洗时可直接用自动属性代替.
          
          所以自动属性只能通过构造函数去限制字段。否则就写成普通属性进行限制

        public class Person
        {
            private string _name;

            public string Name//普通属性
            {
                get { return _name; }
                set { _name = value; }
            }

            public int Age//自动属性
            {
                get;
                set;
            }
        }


    
    
    2、接口简介
        由于类的单根性,只能继承一个父类。当你想继承两个父类时,就显得尴尬了。
        接口是一个规范,一个能力。
        
        语法:
        [public] interface I...able //接口名
        {
            成员;
        }
        
        接口中的成员不允许添加访问修饰符,默认就是public.
        
        接口成员不能有定义。不能有方法体;同样不能有字段名,但可以有属性
        这个属性只能是自动属性,因为自动属性没有方法体。
        因为接口起到桥梁作用,不起到存储数据作用。可以限定相互的规范(属性)
        
        接口里面只能有方法、属性、索引器.本质上都是方法,即接口里都是方法。

        public interface IFlyable
        {
            void Fly();

            //int ID;//错误不能有字段
            string Name //属性能只写成自动属性
            {
                get;
                set;
            }

            string GetValue();
        }    


    
    3、接口的特点
    
        1)接口是一种规范,只要一个类继承了一个接口,这个类就必须实现这个接口中的所有成员
          这和抽象类一样,父类里没有方法体,子类必须重写实现。
        
        2)为了多态,接口不能被实例化,也即:接口不能new(不能创建对象).
            因为接口没有方法体,是不能进行实例创建的。不能实例化的还有静态类,抽象类。
            不同的类用相同的接口,于是调用接口实现多态。
        
        3)接口中的成员不能加访问修饰符,接口中的成员访问修饰符为public,不能修改
            接口中的成员不能有任何实现--光说不做.只是定义了一组未实现的成员。
        
        4)接口中只能有方法、属性、索引器,事件。不能有字段和构造函数。
        
        5)接口与接口之间可以继承,并且可以多继承。可以多接口,但类不能进行多继承。

            public interface M1
            {
                void look1();
            }

            public interface M2
            {
                void look2();
            }

            public interface M3
            {
                void look3();
            }

            public interface ISuperM : M1, M2, M3
            {//接口继承,里面隐含look1-3
                void look4();
            }

            public class Car : ISuperM
            {//四个实现方法缺一不可,否则出错
                //void M1.look1() //出错,必须全部实现.故此方法也得实现
                //{ }

                void M2.look2()
                { }

                void M3.look3()
                { }

                void ISuperM.look4()
                { }
            }


        
        6)接口并不能继承一个类,而类可以继承一个接口。
            类既可以继承类,也可以继承接口。
            接口能够继承接口,但不能继承类。

            public class Person
            {
            }

            public interface M1:Person//错误,接口不能继承类
            {
                void Walk1();
            }


        
        7)实现接口的子类必须实现该接口的全部成员。
        
        8)一个类可以同时继承一个类并实现多个接口,如果一个子类同时继承了父类A,
            并实现了接口IA,那么语法上A必须写在IA的前面。
            一句话:同时继承类和接口,则类必须写在前面。
            因为类是单继承,接口可以多继承,这样多个接口可以连续写在后面。

            public class Person
            {
            }

            public interface Idoable
            {
                void Walk();
            }

            public class Student : Idoable, Person//错误,接口应在类后
            {
                public void Walk()
                {
                }
            }

            public class Student : Person, Idoable
            {
                public void Walk()
                {
                }
            }


        
        与其说是面向对象编程,不如说是面向接口编程。
            
        当一个抽象类实现接口的时候,需要子类去实现接口。

            internal class Program
            {
                private static void Main(string[] args)
                {
                    //IFlyable Ifly=new IFlyable();//错误接口不能实例化
                    IFlyable Ifly = new Person();//Person必须有接口
                    Ifly.Fly();//人类能飞吗?
                    Ifly = new Bird();
                    Ifly.Fly();//鸟儿能飞.    同样的形式不同的结果,多态
                    Console.ReadKey();
                }
            }

            public class Person : IFlyable
            {
                public void Fly()
                { Console.WriteLine("人类能飞吗?"); }
            }

            public class Bird : IFlyable
            {
                public void Fly()
                { Console.WriteLine("鸟儿能飞"); }
            }

            public interface IFlyable
            {//接口内部只能是方法:方法、属性(自动属性)、索引器
                //不允许有访问修饰符,默认为public
                void Fly();
            }


    
    
    4、接口的练习
    
        麻雀能飞,鹦鹉能飞,鸵鸟不会飞,企鹅不会飞,直升飞机会飞。
        用多态来实现。需要方法、抽象类、接口
       分析:1.用鸟的实体会飞方法不能解决“不会飞”的情况;
             2.用鸟的会飞的抽象方法,不能通用解决不会飞的情况。
        由此:是否会飞是一种能力,要用接口来实现。
        同时不是所有鸟会飞,因此鸟这个父类不得有会飞接口。
        鸟儿可以规定它的共性:生命、翅膀、两只脚等等。
        同时,说话也是一种能力,也可以用接口。
        于是:一个基类:鸟;两种接口:说话能力、飞会能力。
        上面来组装题目。比如,鹦鹉用鸟父类两接口
        直升机用会飞接口

        internal class Program
        {
            private static void Main(string[] args)
            {//接口的作用就是多态。否则分别创建各自对象,各自方法,代码繁琐混乱
                IFlyable ifly = new Parrot();
                ifly.Fly();//统一对象,统一方法,简单方便
                ISpeak ispk = new Parrot();
                ispk.Speak();
                ifly = new Sparrow();
                ifly.Fly();
                Console.ReadKey();
            }
        }

        public class Bird
        {//只能作父类,不能有会飞接口,因为有些不会飞
            public void HaveWing()
            { Console.WriteLine("鸟有翅膀."); }

            public void HaveLife()
            { Console.WriteLine("鸟有生命."); }
        }

        public interface IFlyable
        {//会飞的接口
            void Fly();
        }

        public interface ISpeak
        {//说话能力不要与飞的能力混合。每种接口能力应清晰有界限
            void Speak();
        }//因为有些不能同时具备两种能力。

        public class Parrot : Bird, IFlyable, ISpeak
        {
            public void Speak()
            { Console.WriteLine("鹦鹉能说话."); }

            void IFlyable.Fly()
            { Console.WriteLine("鹦鹉能飞"); }
        }

        public class Sparrow : Bird, IFlyable
        {
            public void Fly()
            { Console.WriteLine("麻雀能飞."); }
        }

        public class Ostrich : Bird//没啥能力,只归父类鸟
        { }

        public class Penguin : Bird//同样没能力
        { }

        public class Helicopter : IFlyable //不属鸟,只能飞能力
        {
            public void Fly()
            { Console.WriteLine("直升飞机能飞。"); }
        }


        
    5、显式实现接口
        显式实现接口的目的:解决方法的重名问题。
        
        未继承接口时,类中方法是属性类的。但是如果用了接口同名方法后,
        这个类中的方法不再属于类,而是接口的。怎么将原来类的方法不属于
        接口??
          显式地声明一个接口同名方法,即类中在方法前加上接口名,指明这个
        方法是属于接口,原同名的方法仍然还是类中的方法。

 

        internal class Program
        {
            private static void Main(string[] args)
            {
                IFlyable ifly = new Bird();
                ifly.Fly();//接口的鸟在飞
                Bird f = new Bird();
                f.Fly();//类中的鸟在飞
                f = (Bird)ifly;
                f.Fly();//类中的鸟在飞
                Console.ReadKey();
            }
        }

        public class Bird : IFlyable
        {
            public void Fly()//下面显式声明了Fly,本处Fly则属于类
            { Console.WriteLine("类中的鸟在飞"); }

            void IFlyable.Fly()//前面不得用public,否则错误
            { Console.WriteLine("接口的鸟在飞"); }//显式声明接口方法,属于接口
        }

        public interface IFlyable
        {
            void Fly();
        }


    
    说明:类中默认是private成员,可以添加访问修饰符private(一般都应添加以便显式识别)
        接口中默认是public,如果显式实现接口,7.3版本禁止添加public,会提示错误。
        同样在类中接口显式的实现,相当于接口的延伸,也不得添加public。
        
        但是,如果是隐式实现,必须前面加public,否则会出错。
    
    结论:子类中隐式实现的接口方法,属于子类;应该用public,不然接口无法调用而错误.
          子类中显式实现的接口方法,属于接口;不能用修饰符,默认为public.
    
    可以把接口当作实现接口类的最简的“父类”。IFlyable是Bird的"父类".
    当调用接口的方法时,就会如同抽象类一样,被接口的“子类”同名方法重写了。
    
    什么时候显式的去实现接口:
        当继承的接口中的方法与参数一模一样的时候,要是用显式的实现接口。以便两者区分.
    


九、小结


    什么时候用虚方法来实现多态?
    什么时候用抽象类来实现多态?
    什么时候用接口来实现多态?

        提供的多个类,如果可以从中抽象出一个父类,并且在父类必须写上这几个子类共有的
    方法(提炼出来的方法),此时,
        共有的方法不知道怎么实现,只能在子类去实现,用抽象类;
        共有的方法知道实现,并且还创建父类的对象进行初始化,用虚方法;
            
      接口则是这几个类无法找出共同的父类,但是可以找出它们有共同的能力、共同的行为。
      (鸟与直升机无法找出共同的父类,但可以找出共同的能力--会飞)
            
    练习一:真的鸭子会游泳,木头鸭子不会游泳,橡皮甲子会游泳
        会干什么、能干什么,表明一种能力,用接口。
        尽管可以真鸭子作父类,但不能把游泳作为共同方法,因为木头不会游泳。虚方法pass.
        由于真鸭子需要创建对象,不能用抽象方法。

        internal class Program
        {
            private static void Main(string[] args)
            {
                ISwimable isw = new RealDuck();
                isw.Swim();//真的鸭子会嘎嘎游
                isw = new RubberDuck();
                isw.Swim();//橡皮鸭子飘着游
                Console.ReadKey();
            }
        }

        public class RealDuck : ISwimable
        {
            public void Swim()
            { Console.WriteLine("真的鸭子会嘎嘎游"); }
        }

        public class WoodDuck
        {
        }

        public class RubberDuck : ISwimable
        {
            public void Swim()
            { Console.WriteLine("橡皮鸭子飘着游"); }
        }

        public interface ISwimable
        {
            void Swim();
        }


    
    分析一:

        public class RealDuck : ISwimable
        {
            public void Swim()
            { Console.WriteLine("真的鸭子会嘎嘎游"); }
        }

        public class RubberDuck : RealDuck, ISwimable
        {
        }

        public interface ISwimable
        {
            void Swim();
        }


        橡皮鸭Rubber未实现接口方法Swim,不会报错,因为父类真实鸭RealDuck已经
    实现,由父类继承到子类,子类中就有实现的Swim方法了。
        父类RealDuck中的接口方法Swim,是属于RealDuck,不是ISwimable的。因为它
    是隐式的,所以前面加了public.
    
    分析二:将分析一中的RubberDuck改成下面:

        public class RubberDuck : RealDuck, ISwimable
        {
            void Swim()
            {
                Console.WriteLine("橡皮同名鸭子");
            }
        }


        类内Swim是隐式接口方法,属于RubberDuck,所以默认前面是private,由于与父类
    中RealDuck中的Swim重名,当ISwimable isw = new RubberDuck()时,调用:
            isw.Swim();//真的鸭子会嘎嘎游
        可以看到,本类中是private不会调用,直接调用了父类中的真鸭子。
        同时该Swim会警告提示,因为这个重名了,若故意隐藏父类重名应用New来表示:
            private new void Swim() 
        尽管不会警告,仍会提示不会使用,因为是private,改成public,调用上面:
            isw.Swim();//橡皮同名鸭子
        这样,重载就直接调用的是本类橡皮鸭子的Swim了。
        注意,这里不能因重载,前面写成public override void Swim(),这样会出错,
    因为这里override只能与virtual配套使用。即前面要有虚方法。或者是抽象方法。
    


十、GUID(全局唯一标识符)


        GUID(Globally Unique Identifier)全局唯一标识符,是一种由算法生成的二进制
    长度为128位的数字标识符。GUID主要用于在拥有多个节点、多台计算机的网络或系统中。
        在理想情况下,任何计算机和计算机集群都不会生成两个相同的GUID。GUID的总数
    达到了2^128(3.4×10^38)个,所以随机生成两个相同GUID的可能性非常小,但并不为0。
    所以,用于生成GUID的算法通常都加入了非随机的参数(如时间),以保证这种重复的
    情况不会发生。
    

        Guid.NewGuid();//产生一个不会重复的ID

    
十一、项目:超市收银系统


    超市分三大块:
    1)商品:有商品ID(GUID码,唯一码)、价格、上货数量
        为了便于管理,建立一个商品父类,包括上面三个属性内容。
    2)仓库:1存储货物;2提货;3进货。
    3)收银:
    
    第一步:建立商品类,假定商品有Acer、banana、JiangYou(酱油)、Samsung四类
            其中它们的父类ProductFather.

        internal class ProductFather
        {//商品父类
            public string ID { get; set; }

            public double Price { get; set; }

            public string Name { get; set; }

            public ProductFather(string id, double price, string name)
            {
                this.ID = id;
                this.Price = price;
                this.Name = name;
            }
        }


        //下面四个子类

        internal class Acer : ProductFather
        {
            public Acer(string id, double price, string name) : base(id, price, name)
            { }
        }
        internal class JiangYou : ProductFather
        {
            public JiangYou(string id, double price, string name) : base(id, price, name)
            { }
        }
        internal class SamSung : ProductFather
        {
            public SamSung(string id, double price, string name) : base(id, price, name)
            { }
        }
        internal class Banana : ProductFather
        {
            public Banana(string id, double price, string name) : base(id, price, name)
            { }
        }


        
        
    第二步:建立仓库类(重点)
        存储货物,用数组不可取,长度不能伸缩。
        下面用集合也不可取,4种就要4个集合,10000种商品就要10000个集合:(,程序员
    添加商品都会写10000行....
        List<SamSung> lstSam=new List<SamSung>();
        List<Banana> lstBan=new List<Banana>();
        List<JiangYou> lstJY=new List<JiangYou>();
        List<Acer> lstAcer=new List<Acer>();
        //.....
        所以第一步中的商品类父类,ProductFather就很重要了,它屏蔽了各种商品的差异
    性,于是就用父类来解决:
        List<ProductFather> lst1 = new List<ProductFather>();
        但是,这只是一种类型:父类。无法区别子类商品。仓库时货物都是分门别类,排
    列整齐,存储与放置都很方便的。而不是直接用父类(无法区别子类)进行混合存放。
        所以好像又是死路了。用字典键值对,一样,也是有一个商品一个数据,雷同于最
    开始的10000种商品的做法,也行不通。
    
        集合组成的集合:
 

        List<List<ProductFather>> lst2 = new List<List<ProductFather>>();


        注意:lst1是由ProductFather类型元素组成的集合,有四种类型元素;
              lst2是由集合list<ProductFather>组成的。因此list2的元素是集合.
    
        仓库由很多货架组成,每个货架摆放的是同一类型商品。
    因此每一个货架是一个集合,由同一种类型的商品(元素)组成。
    仓库是大集合,由每个货架(集合)作为元素来组合成仓库。题中有商品对应四个货架.
        所以上面集合组成的集合,lst2[0]-lst2[3]分别对应四个货架,是四个集合,
    每一种集合存储对应的一种商品元素.
    
        一句话,现在没添加货物,只是仓库初始化。(以便后加添加货架)
    因为其构造函数,可以如下写四句就是四个货架:

        lst2.Add(new List<ProductFather>());


        但这仍然是有多少商品写多少货架。可是,这个货架的形式一样,对上一句,就可
    以用循环了,四个货架循环四次,10000个货架循环10000次。
    
    流程:仓库用构造函数时,就会创建四个空的货架(无货),还需要添加货物。再通过方法
        进货进行添加到货架。取货时返回到父类

    internal class CangKu
    {
        //private List<ProductFather> lis1 = new List<ProductFather>();//与下一句有区别
        private List<List<ProductFather>> lst2 = new List<List<ProductFather>>();//泛型嵌套

        public CangKu()
        {
            lst2.Add(new List<ProductFather>());
            lst2.Add(new List<ProductFather>());
            lst2.Add(new List<ProductFather>());
            lst2.Add(new List<ProductFather>());
        }//lst[0]Acer, lst2[1]SamSung, lst2[2]JiangYou, lst2[3]Banana

        public void JinPros(string strType, int count)
        {//逐步将同种商品入库,生成唯一码对应每一个商品ID
            for (int i = 0; i < count; i++)
            {
                switch (strType)//疑惑:同样商品10000种,分支10000种?
                {
                    case "Acer":
                        lst2[0].Add(new Acer(Guid.NewGuid().ToString(), 1000, "宏基电脑"));
                        break;

                    case "SamSung":
                        lst2[1].Add(new SamSung(Guid.NewGuid().ToString(), 1000, "三星手机"));
                        break;

                    case "JiangYou":
                        lst2[2].Add(new JiangYou(Guid.NewGuid().ToString(), 1000, "酱油路人"));
                        break;

                    case "Banana":
                        lst2[3].Add(new Banana(Guid.NewGuid().ToString(), 1000, "香蕉你个菠萝"));
                        break;
                }
            }
        }

        public ProductFather[] QuPros(string strType, int count)//返回父类屏蔽子类差异
        {//取货,取多个固定大小用数组,从集合中移除有的货物
            ProductFather[] pros = new ProductFather[count];
            for (int i = 0; i < count; i++)
            {
                switch (strType)
                {
                    case "Acer":
                        if (lst2[0].Count == 0) break;//货物空,自然返回数组中元素为null
                        pros[i] = lst2[0][0];
                        lst2[0].RemoveAt(0);//移除索引0,后面自动填充索引移动,不是元素移动
                        break;              //即原索引1变成移后索引0,后面以此类推

                    case "SamSung":
                        if (lst2[1].Count == 0) break;
                        pros[i] = lst2[1][0];//移除之前判断仓库是否有货,才能安全地移除
                        lst2[1].RemoveAt(0);//也可写成lst2[1].RemoveAt(lst2[1].Count - 1);
                        break;

                    case "JiangYou":
                        if (lst2[2].Count == 0) break;
                        pros[i] = lst2[2][0];
                        lst2[2].RemoveAt(0);
                        break;

                    case "Banana":
                        if (lst2[3].Count == 0) break;//如果一直没货,返回数组元素即为null
                        pros[i] = lst2[3][0];
                        lst2[3].RemoveAt(0);
                        break;
                }
            }
            return pros;
        }

        public void ShowPros()//展示货物有哪些
        {
            foreach (var item in lst2)
            {
                Console.WriteLine("超市有{0}货物{1}个,每个{2}元", item[0].Name, item.Count, item[0].Price);
            }
        }
    }


    
    
    
    第三步,超市类
        创建仓库(初始化实例),创建超市:进货,出货,计算金额。还有结算打折。
        但是打折有很多种,无法固定,因此需要有一个父类来完善。父类无需知道打折的
    实现,根据输入的选择,由子类来具体实现打折的活动。故父类应是抽象类CalFather.
    与前面的简单工厂设计模式相同:根据用户的输入返回一个父类对象(GetCal)
        在未创建仓库时,仓库中是无货物,只有创建超市时里面创建了仓库,加入了货物,
    此时就可以显示ShowPors(),该方法内部去调用ck.ShowPros();

    internal class SupperMarket
    {
        private CangKu ck = new CangKu();

        public SupperMarket()
        {//必须与进化中的CangKu类中JinPros()中货物字串相匹配,不然无法进入货架
            ck.JinPros("Acer", 1000);
            ck.JinPros("SamSung", 1000);
            ck.JinPros("JiangYou", 1000);
            ck.JinPros("Banana", 1000);
        }

        public void AskBuying()//询问并取货
        {
            Console.WriteLine("欢迎光临,请问您需要什么?");
            Console.WriteLine("我们有Acer,SamSung,Jianyou,Banana:");
            string strTypte = Console.ReadLine();
            Console.WriteLine("您需要多少?");
            int count = Convert.ToInt32(Console.ReadLine());
            //取货
            ProductFather[] pros = ck.QuPros(strTypte, count);
            double realmoney = GetMoney(pros);//总金额
            Console.WriteLine("你总共应付{0}元", realmoney);
            //打折
            Console.WriteLine("选择打折方式:1.不打折,2.打九折,3.打85折,4.买300送50,5.买500送100");
            string input = Console.ReadLine();
            //通过简单工厂的设计模式。根据用户输入获得一个打折对象
            CalFather cal = GetCal(input);//返回父类.
            //调用父类方法。实则在多态在子类中重写。
            double totalMoney = cal.GetTotalMoney(realmoney);
            Console.WriteLine("打折后应付{0}元.", totalMoney);
            Console.WriteLine("你的购物清单:");
            foreach (var item in pros)
            {
                Console.WriteLine("货物编号:{0},\t货物名称:{1},\t货物人价格:{2}", item.ID, item.Name, item.Price);
            }
        }

        public CalFather GetCal(string input)//简单工厂类
        {
            CalFather cal = null;
            switch (input)
            {
                case "1": cal = new CalNormal(); break;
                case "2": cal = new CalRate(0.9); break;
                case "3": cal = new CalRate(0.85); break;
                case "4": cal = new CalMN(300, 50); break;
                case "5": cal = new CalMN(500, 100); break;
            }

            return cal;
        }

        public double GetMoney(ProductFather[] pros)//计算金额
        {
            double realmoney = 0;
            //第一种:有bug
            //for (int i = 0; i < pros.Length; i++)
            //{
            //    realmoney += pros[i].Price;//可改成pros[0]
            //}

            //第二种:也有bug
            //realmoney = pros[0].Price * pros.Length;

            //第三种:因为返回的父类数组pros[]可能不是满的,甚至全部元素为空。
            int i = 0;
            while (pros[i] != null)
            {
                realmoney += pros[i].Price;//考虑到以后可能不同货物,用i而不用0
                if (++i > pros.Length - 1) break;
            }
            return realmoney;
        }

        public void ShowPros()
        {
            ck.ShowPros();
        }
    }


    
    超市类中要用到简单工作设计模式的打折写法。
    由父类CalFather抽象方法,到三个子类CalNormal,CalRate,CalNM去实现:

    internal abstract class CalFather
    {
        /// <summary>
        /// 计算打折后的金额
        /// </summary>
        /// <param name="realmoney">折前金额</param>
        /// <returns>折后金额</returns>
        public abstract double GetTotalMoney(double realmoney);
    }
    internal class CalNormal : CalFather
    {
        public override double GetTotalMoney(double realmoney)
        {
            return realmoney;
        }
    }
    internal class CalMN : CalFather
    {//买M送N
        public double M { get; set; }

        public double N { get; set; }

        public CalMN(double m, double n)
        {
            this.M = m; this.N = n;
        }

        public override double GetTotalMoney(double realMoney)
        {
            if (realMoney >= this.M)
            {
                return realMoney - (int)(realMoney / this.M) * this.N; //几个M送几N
            }
            else
            {
                return realMoney;
            }
        }
    }
    internal class CalRate : CalFather
    {
        public double Rate
        {
            get; set;
        }

        public CalRate(double rate)//创建时就取得折率
        {
            this.Rate = rate;
        }

        public override double GetTotalMoney(double realmoney)
        {//需提前知道折率
            return realmoney * this.Rate;
        }
    }


    
    第四步,在主函数操作
        创建超市对象(内含货物进入),然后显示一下货物。
        然后与用户交互:进行商品交易,金额打折,以及小票显示。

    internal class Program
    {
        private static void Main(string[] args)
        {
            SupperMarket sm = new SupperMarket();
            sm.ShowPros();
            sm.AskBuying();
            sm.ShowPros();
            Console.ReadKey();
        }
    }


    
    程序运行操作显示:
        超市有宏基电脑货物1000个,每个1000元
        超市有三星手机货物1000个,每个1000元
        超市有酱油路人货物1000个,每个1000元
        超市有香蕉你个菠萝货物1000个,每个1000元
        欢迎光临,请问您需要什么?
        我们有Acer,SamSung,Jianyou,Banana:
        Acer
        您需要多少?
        11
        你总共应付11000元
        选择打折方式:1.不打折,2.打九折,3.打85折,4.买300送50,5.买500送100
        3
        打折后应付9350元.
        你的购物清单:
        货物编号:ef76a5c7-29c3-45b7-8938-d8aa8a8896df,  货物名称:宏基电脑,      货物人价格:1000
        货物编号:72128793-908c-496d-9195-50e228bffe27,  货物名称:宏基电脑,      货物人价格:1000
        货物编号:faec6c84-c3e7-4830-8714-2148f9156602,  货物名称:宏基电脑,      货物人价格:1000
        货物编号:7483ad5e-78d2-4f05-a2ed-99319bf9c3d9,  货物名称:宏基电脑,      货物人价格:1000
        货物编号:a1908cf1-5763-4e42-9d88-425dd10d15cf,  货物名称:宏基电脑,      货物人价格:1000
        货物编号:7a0497fb-9bff-450b-aa14-5da0ad27b4c0,  货物名称:宏基电脑,      货物人价格:1000
        货物编号:47e3cf90-a072-4a16-8f3a-a1387c50aac2,  货物名称:宏基电脑,      货物人价格:1000
        货物编号:2b0c57fe-0732-4fe1-a182-c8ba302bf578,  货物名称:宏基电脑,      货物人价格:1000
        货物编号:6cd32184-26e8-4598-b3cf-8f724e8b6153,  货物名称:宏基电脑,      货物人价格:1000
        货物编号:c8cc38a7-fd17-49a4-ac68-14b83f961620,  货物名称:宏基电脑,      货物人价格:1000
        货物编号:b2cc1f9c-94ac-47d5-abb7-aaa871d4e9aa,  货物名称:宏基电脑,      货物人价格:1000
        超市有宏基电脑货物989个,每个1000元
        超市有三星手机货物1000个,每个1000元
        超市有酱油路人货物1000个,每个1000元
        超市有香蕉你个菠萝货物1000个,每个1000元    
    
    

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值