(13)C#传智:访问修饰符,简单工厂模式,序列化与反序列化,部分类,密封类,接口(第13天)
创始人
2024-06-01 02:35:16
0

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

    
    深入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 Luck;
            第三步,在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 BaseClassvar b = new BaseClass();//Program继承于BaseClass,可访问父类的proctected myValuevar 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:20Person 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);//10TestIntRef(ref n3); //引用传递Console.WriteLine(n3);//11Console.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 HardConsole.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-3void 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{//接口内部只能是方法:方法、属性(自动属性)、索引器//不允许有访问修饰符,默认为publicvoid 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 lstSam=new List();
        List lstBan=new List();
        List lstJY=new List();
        List lstAcer=new List();
        //.....
        所以第一步中的商品类父类,ProductFather就很重要了,它屏蔽了各种商品的差异
    性,于是就用父类来解决:
        List lst1 = new List();
        但是,这只是一种类型:父类。无法区别子类商品。仓库时货物都是分门别类,排
    列整齐,存储与放置都很方便的。而不是直接用父类(无法区别子类)进行混合存放。
        所以好像又是死路了。用字典键值对,一样,也是有一个商品一个数据,雷同于最
    开始的10000种商品的做法,也行不通。
    
        集合组成的集合:
 

        List> lst2 = new List>();


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

        lst2.Add(new List());


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

    internal class CangKu{//private List lis1 = new List();//与下一句有区别private List> lst2 = new List>();//泛型嵌套public CangKu(){lst2.Add(new List());lst2.Add(new List());lst2.Add(new List());lst2.Add(new List());}//lst[0]Acer, lst2[1]SamSung, lst2[2]JiangYou, lst2[3]Bananapublic void JinPros(string strType, int count){//逐步将同种商品入库,生成唯一码对应每一个商品IDfor (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;//货物空,自然返回数组中元素为nullpros[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;//如果一直没货,返回数组元素即为nullpros[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而不用0if (++i > pros.Length - 1) break;}return realmoney;}public void ShowPros(){ck.ShowPros();}}


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

    internal abstract class CalFather{/// /// 计算打折后的金额/// /// 折前金额/// 折后金额public abstract double GetTotalMoney(double realmoney);}internal class CalNormal : CalFather{public override double GetTotalMoney(double realmoney){return realmoney;}}internal class CalMN : CalFather{//买M送Npublic 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元    
    
    

相关内容

热门资讯

122.(leaflet篇)l... 听老人家说:多看美女会长寿 地图之家总目录(订阅之前建议先查看该博客) 文章末尾处提供保证可运行...
育碧GDC2018程序化大世界... 1.传统手动绘制森林的问题 采用手动绘制的方法的话,每次迭代地形都要手动再绘制森林。这...
育碧GDC2018程序化大世界... 1.传统手动绘制森林的问题 采用手动绘制的方法的话,每次迭代地形都要手动再绘制森林。这...
Vue使用pdf-lib为文件... 之前也写过两篇预览pdf的,但是没有加水印,这是链接:Vu...
PyQt5数据库开发1 4.1... 文章目录 前言 步骤/方法 1 使用windows身份登录 2 启用混合登录模式 3 允许远程连接服...
Android studio ... 解决 Android studio 出现“The emulator process for AVD ...
Linux基础命令大全(上) ♥️作者:小刘在C站 ♥️个人主页:小刘主页 ♥️每天分享云计算网络运维...
再谈解决“因为文件包含病毒或潜... 前面出了一篇博文专门来解决“因为文件包含病毒或潜在的垃圾软件”的问题,其中第二种方法有...
南京邮电大学通达学院2023c... 题目展示 一.问题描述 实验题目1 定义一个学生类,其中包括如下内容: (1)私有数据成员 ①年龄 ...
PageObject 六大原则 PageObject六大原则: 1.封装服务的方法 2.不要暴露页面的细节 3.通过r...
【Linux网络编程】01:S... Socket多进程 OVERVIEWSocket多进程1.Server2.Client3.bug&...
数据结构刷题(二十五):122... 1.122. 买卖股票的最佳时机 II思路:贪心。把利润分解为每天为单位的维度,然后收...
浏览器事件循环 事件循环 浏览器的进程模型 何为进程? 程序运行需要有它自己专属的内存空间࿰...
8个免费图片/照片压缩工具帮您... 继续查看一些最好的图像压缩工具,以提升用户体验和存储空间以及网站使用支持。 无数图像压...
计算机二级Python备考(2... 目录  一、选择题 1.在Python语言中: 2.知识点 二、基本操作题 1. j...
端电压 相电压 线电压 记得刚接触矢量控制的时候,拿到板子,就赶紧去测各种波形,结...
如何使用Python检测和识别... 车牌检测与识别技术用途广泛,可以用于道路系统、无票停车场、车辆门禁等。这项技术结合了计...
带环链表详解 目录 一、什么是环形链表 二、判断是否为环形链表 2.1 具体题目 2.2 具体思路 2.3 思路的...
【C语言进阶:刨根究底字符串函... 本节重点内容: 深入理解strcpy函数的使用学会strcpy函数的模拟实现⚡strc...
Django web开发(一)... 文章目录前端开发1.快速开发网站2.标签2.1 编码2.2 title2.3 标题2.4 div和s...