由于很久没有碰过OOP的编程基础,所以记录一下温习的要点。

OOP的三大特征:封装、继承、多态

对象

对象这个东西是个什么?

简单来讲就系它是一个软件构造类,它一般代表着某类事物或者抽象某些东西,例如一个人

进一步讲,类是对象的定义,对象是实例化的类。

一个对象包含有着正在代表的事物的数据、属性,它还能够为正在代表的事物提供功能或方法。比如一个人,属性可以为年龄、姓名、性别等等,功能可以是走路、吃饭或睡觉等。

命名空间

命名空间类似于Java中的概念,但不完全相同。

C# 10 中可使用文件范围的命名空间声明,和Java很像:

namespace SampleNamespace;

class AnotherSampleClass
{
    public void AnotherSampleMethod()
    {
        System.Console.WriteLine(
            "SampleMethod inside SampleNamespace");
    }
}

但在C# 10之前的命名格式:

namespace MyNameSpace
{
    class MyClass
    {
        
    }
}

名字空间是用来按照逻辑,对类进行划分,增强了程序的可读性,而且减少了命名的冲突。

但若都引用了两个命名空间,两个其中的抽象模型名称相同,则在实例化时需要特别指定是使用哪个命名空间的抽象模型实例化成对象。

即:

...
//下面两个命名空间有相同名称的抽象模型Student
using ClassFourTwo;
using ClassThreeOne;

namespace test
{
    class Program
    {
        static void Main(string[] args)
        {
            ClassFourTwo.Student student1 = new ClassFourTwo.Student();
            ClassThreeOne.Student student2 = new ClassThreeOne.Student();
        }
    }
}

Java的package本身没有子包的概念,所有package都是并列的关系,没有谁包含谁的问题。比如:org.dominoo.action和org.dominoo.action.asl之间绝对没有包与子包的关系。它们是各自独立的包,各自拥有自己的class/interface的集合。在org.dominoo.action.asl的某个java文件里,如果想引用org.dominoo.action里的某个class/interface,则必须import org.dominoo.action

另外,C#命名空间只是一种逻辑上的结构,它允许所组织的类文件的物理存放位置与逻辑结构不一致,而Java中类文件的物理结构必须与逻辑结构一致。

C# 中命名空间可以被嵌套,即您可以在一个命名空间内定义另一个命名空间。亦就是有主包和子包的概念。

例:

namespace MyCompany.Proj1
{
    class MyClass
    {
    }
}

namespace MyCompany.Proj1
{
    class MyClass1
    {
    }
}

另例:

namespace SomeNameSpace
{
    public class MyClass
    {
        static void Main()
        {
            Nested.NestedNameSpaceClass.SayHello();
        }
    }

    // 一个嵌套命名空间
    namespace Nested
    {
        public class NestedNameSpaceClass
        {
            public static void SayHello()
            {
                Console.WriteLine("Hello");
            }
        }
    }
}
// 输出: Hello

常用类库记录:

using System; //包含定义数据类型,事件,事件处理程序等基本类
using System.Data; //包含了提供数据访问功能的命名空间和类
using System.IO; //包含数据流和读写相关功能的类
using System.Web; //包含网站开发相关的命名空间和类

Encapsulation (封装)

封装是为了隐藏对象的属性和实现细节,仅对外提供公共访问形式。封装能够提高安全性,避免用户对类中属性或方法做出不合理的操作;且封装能够降低复杂度,你仅需了解对象能够为你做些什么,而不需要在意其中的细节。

原则:

  • 将不需要对外提供的功能进行隐藏。
  • 将属性隐藏,转而提供公共方法对其操作。

Inheritance (继承)

继承可使我们能够创建重用、扩展和修改在其他类中定义的method的新类。成员被继承的类称为基类,继承这些成员的成为派生类(对比于Java中的父类和子类)。一个派生类只能有一个直接基类。

继承是可传递的。例如ClassC派生于ClassBClassB派生于ClassA,则ClassC继承了ClassAClassB中声明的成员。

在C#中,所有的类都直接或间接的继承自object类(当我们定义一个类的时候,如果没有给该类指定继承一个类,那么这个类就继承了object类)。

在接口例子中展现了继承的用法。

polymorphism(多态)

多态多态,意为多种形态,这就是很直观的一个解释。一般而言,多态有三种表现形式。

  • 第一种多态类型,在同一个类中拥有多个方法名称,这一般叫做方法重载。例如一个类上有多个构造函数,但是每一个构造函数有着不同的参数。

  • 第二种多态类型为参数化多态,如泛型。通过泛型,你可以在同一个类中引用不同的数据类型。例如当一个操作与参数类型无关,而希望对各种参数类型都适用时,这里就是泛型适用的场合。

public class Elements<T>{
    private T t;
    public add(T t){
        this.t=t;    
    }
    public get(){
        return t;
    }
}
 
public static void main(String[] args){
    Elements<Integer> n=new Elements<Integer>();
    n.add(12);
    System.out.println(n.get());
 
    Elements<String> s=new Elements<String>();
    s.add("abcd");
    System.out.println(s.get());
}

由于实现的操作与类型完全无关,所以我们就可以在定义类的时候使用泛型,在具体实现的时候再对具体的类型做出定义。

  • 第三种为子类型多态(同一个对象可能属于多种类型),例如:继承、重写。
class Program
{
   static void Main(string[] args)
   {
       MakeNoise(new Ford());
       MakeNoise(new Beetle());
       Console.ReadLine();
   }
  
   private static void MakeNoise(Car c)
   {
       Console.WriteLine(c.Honk());
   }
}
  
class Ford : Car
{
   public override string Honk()
   {
       return "Honk!";
   }
}
  
class Beetle : Car
{
   public override string Honk()
   {
       return "Beep! Beep!";
   }
}
  
abstract class Car
{
    public abstract string Honk();
}

Interface(接口)

概念方面?我自己都看不明白

接口定义了所有类继承接口时应遵循的语法合同。接口定义了语法合同 “是什么” 部分,继承于接口的派生类定义了语法合同 “怎么做” 部分。

接口只定义接口成员的声明,成员包括属性,方法,事件。

接口只包含了成员的声明。成员的实现是派生类的责任。

接口提供了派生类应遵循的标准结构。接口使得实现接口的类或结构在形式上保持一致。

接口的定义使用interface关键字,在关键词后为接口名称,通常以大写字母I开头。且接口不需要访问修饰符,因为接口本身是对外调用的,默认为public状态。

namespace demo
{
    
    interface IMyInterface
    {
        // 定义方法 输出内容等(是什么)
        void WriteLine();
    }
    
    // 继承于接口的类,必须要实现所有的接口成员(怎么做)
    class MyWriteSome : IMyInterface
    {
        public void WriteLine()
        {
            Console.WriteLine("输出文字");
        }
    }
}
  • 接口可以继承接口。
  • 接口中定义的类,包括多个接口,在继承于接口的派生类中必须要全部实现。
  • 接口不能实例化。
  • 类可以继承类,但只能继承一个基类;类可以继承多个接口。
  • 接口声明时可以为其成员定义默认实现。

简单的一个现实实际例子:


再展示一个Java语言的例子:

定义一个接口用来实现两个对象的比较。

package cn.com.A114514;

public interface CompareObject<object> {
    public int compareto(object o);
    //若返回值是 0 , 代表相等; 若为正数,代表当前对象大;负数代表当前对象小
}

定义一个Circle类。

package cn.com.A114514;

public class Circle {
    private double radius;
    private final double PI=3.14;

    public double findArea(){
        return PI*radius*radius;
    }

    public double getRadius() {
        return radius;
    }

    public void setRadius(double radius) {
        this.radius = radius;
    }
}

定义一个ComparableCircle类,继承Circle类并且实现CompareObject接口。在ComparableCircle类中给出接口中方法compareTo的实现体,用来比较两个圆的半径大小。

package cn.com.A114514;

public class ComparableCircle extends Circle implements CompareObject<Circle> {
    @Override
    public int compareto(Circle o) {
        if(this.getRadius()==o.getRadius()){
            return 0;
        }else if(this.getRadius()<=o.getRadius()){
            return -1;
        }else {
            return 1;
        }
    }
}

定义一个测试类TestInterface,创建两个ComaparableCircle对象,调用compareTo方法比较两个类的半径大小。

package cn.com.A114514;

import java.util.Scanner;

public class TestInterface {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        ComparableCircle c1 = new ComparableCircle();
        ComparableCircle c2 = new ComparableCircle();
        c1.setRadius(sc.nextDouble());
        c2.setRadius(sc.nextDouble());
        System.out.println(c1.compareto(c2));
    }
}

接下来应该会重新学习一下SOLID原则和松紧耦合,这些说实话在大学课程的《软件工程设计》课程中我都有学过,可惜的是自己不够用功学习,只能通过写blog的方式进行温习反思。