学习C语言中类之间的关系

203 阅读10分钟

在过去的几个教程中,我们经历了识别几个类的过程,这些类将促进一个基本的CRM应用。在识别阶段,我们确定了业务实体,创建了属性,并确定了方法的范围。接下来,我们把责任分开,目的是为了减少耦合,使代码更简单。现在,我们进入了建立类之间关系的阶段。这些关系定义了对象如何一起工作以完成应用程序运行所需的工作。


关系的类型

在面向对象编程中,有许多类型的关系。我们要看的第一种是协作关系。在协作关系中,你经常把它称为 "使用A "关系。这是因为你可以认为一个类使用另一个类。我们要看的下一种类型的关系是组成关系。组成关系可以用 "拥有A "关系类型来指代。构成的概念是,一个对象可以由其他对象构成。一个订单有一个客户。一个订单也有一个OrderItem。我们要看的最后一种关系类型是继承关系,或者说是 "Is A "关系。一个商业客户是一个客户,或者一个消费者客户是一个客户。


协作关系

这里我们有一个类间协作类型的关系图。OrderRepository "使用 "Order对象在Retrieve中填充,在save中序列化。同样的,CustomerRepository "使用 "Customer对象和ProductRepository "使用 "Product对象。

object-oriented collaboration class relationship

在这段代码中,我们可以看到 CustomerRepository 类是如何使用 Customer 类的。在Retrieve()方法中,我们看到一个新的客户对象是如何被创建的。然后该对象被填充了数据。在我们的例子中,它是简单的硬编码,但你可以想象这些数据来自数据库。

CustomerRepository在Save()方法中再次使用了一个客户对象。为了使该方法完成其工作,它需要接受一个客户对象作为参数。在该方法中,客户对象的数据将被用于持久化到数据库中。存储库类使用一个实体类来填充实体,或者序列化实体,这取决于它是否被用于Retrieve()或Save()方法。这种模式对我们一直在使用的其他资源库类也是如此。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CRMBIZ
{
    public class CustomerRepository
    {
        public Customer Retrieve(int customerId)
        {
            Customer customer = new Customer(customerId);

            if (customerId == 1)
            {
                customer.EmailAddress = "ssmith@greenenergy.com";
                customer.FirstName = "Susan";
                customer.LastName = "Smith";
            }
            return customer;
        }

        public List<Customer> Retrieve()
        {
            return new List<Customer>();
        }

        public bool Save(Customer customer)
        {
            return true;
        }
    }
}

当你看到一个类使用另一个类的实例来执行应用程序中的操作时,你可以发现协作类型的关系。


组成

组成是面向对象编程中的另一个关键关系类型。当一个类的一个对象由另一个或多个类的一个或多个对象组成时,就存在一种组合关系。它也被称为 "有A "型关系。在我们的CRM应用程序中,这种类型的关系存在于客户类和地址类之间。一个客户 "拥有一个 "地址。

object-oriented composition class relationship

在上图中,我们显示了一些组成关系。客户类 "拥有一个 "地址。订单对象也是由其他对象组成的。每个订单都有一个客户,有一个地址,有一个订单项。此外,每个OrderItem "拥有一个 "产品。因此,我们可以看到一个给定的对象是如何由其他对象组成的,以使应用程序工作。


用引用组成

组成关系可以在我们的代码中使用引用来完成,它利用了类的属性。让我们看一下客户类,看看它是如何工作的。首先,我们来看看客户类和地址类之间的组合关系。在下面的代码中,我们强调了一个特定的属性。客户对象是由一个或多个地址对象组成的。为什么是一个或多个?因为可能有一个家庭地址或一个工作地址。所以我们使用一个List类型的属性来允许一个或多个地址对象帮助组成一个客户对象。客户类中的属性声明建立了客户类和地址类之间的组成关系。还要注意的是,由于这个属性是一个List,它必须在构造函数中被初始化,就像我们在下面看到的那样。如果不这样做,就会引起一个空值异常。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CRMBIZ
{
    public class Customer
    {
        public Customer()
            : this(0)
        {

        }

        public Customer(int customerId)
        {
            this.CustomerId = customerId;
            AddressList = new List<Address>();
        }

        public List<Address> AddressList { get; set; }

        public int CustomerType { get; set; }
        public static int InstanceCount { get; set; }

        private string _lastName;
        public string LastName
        {
            get
            {
                return _lastName;
            }
            set
            {
                _lastName = value;
            }
        }

        public string FirstName { get; set; }

        public string EmailAddress { get; set; }

        public int CustomerId { get; private set; }

        public string FullName
        {
            get
            {
                string fullName = LastName;
                if (!string.IsNullOrWhiteSpace(FirstName))
                {
                    if (!string.IsNullOrWhiteSpace(fullName))
                    {
                        fullName += ", ";
                    }
                    fullName += FirstName;
                }
                return fullName;
            }
        }

        public bool Validate()
        {
            var isValid = true;

            if (string.IsNullOrWhiteSpace(LastName)) isValid = false;
            if (string.IsNullOrWhiteSpace(EmailAddress)) isValid = false;

            return isValid;
        }
    }
}

关键的启示是,在组合式关系中,一个类的对象是由另一个类的对象构造的。在本例中,一个客户对象的构造构建了一个地址对象的列表。很多时候,组合关系比继承关系更受欢迎。


填充被引用的对象

为了给客户对象填充数据,我们需要一些代码来完成检索数据的工作。这最好放在AddressRepository类中。它将被用来获取客户的一个或多个地址。就像CRM应用程序中的其他存储库类一样,AddressRepository类有一个Retrieve()和一个Save()方法。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CRMBIZ
{
    public class AddressRepository
    {
        public Address Retrieve(int addressId)
        {
            Address address = new Address(addressId);

            if (addressId == 1)
            {
                address.AddressType = 1;
                address.StreetLine1 = "Microsoft Way";
                address.StreetLine2 = "C# Blvd";
                address.City = "Seattle";
                address.State = "Washington";
                address.Country = "United States";
                address.PostalCode = "98052";
            }
            return address;
        }

        public IEnumerable<Address> RetrieveByCustomerId(int customerId)
        {
            var addressList = new List<Address>();
            Address address = new Address(1)
            {
                AddressType = 1,
                StreetLine1 = "Microsoft Way",
                StreetLine2 = "C# Blvd",
                City = "Seattle",
                State = "Washington",
                Country = "United States",
                PostalCode = "98052"
            };
            addressList.Add(address);

            address = new Address(2)
            {
                AddressType = 2,
                StreetLine1 = "Happy Island",
                City = "Saltwater Shores",
                State = "Washington",
                Country = "United States",
                PostalCode = "98569"
            };
            addressList.Add(address);

            return addressList;
        }

        public bool Save(Address address)
        {
            return true;
        }
    }
}

有趣的是,我们现在可以做的是在CustomerRepository和AddressRepository类之间建立一种协作关系。为了在CustomerRepository中显示对AddressRepository的依赖,我们在类的顶部定义了这种关系,并在构造函数中创建一个实例。此外,私有属性addressRepository现在持有一个AddressRepository对象。Retrieve()方法现在被更新为使用存在于AddressRepository中的RetrieveByCustomerId()方法。这里强调了这段代码。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CRMBIZ
{
    public class CustomerRepository
    {
        private AddressRepository addressRepository { get; set; }

        public CustomerRepository()
        {
            addressRepository = new AddressRepository();
        }

        public Customer Retrieve(int customerId)
        {
            Customer customer = new Customer(customerId);
            customer.AddressList = addressRepository.RetrieveByCustomerId(customerId).ToList();

            if (customerId == 1)
            {
                customer.EmailAddress = "ssmith@greenenergy.com";
                customer.FirstName = "Susan";
                customer.LastName = "Smith";
            }
            return customer;
        }

        public List<Customer> Retrieve()
        {
            return new List<Customer>();
        }

        public bool Save(Customer customer)
        {
            return true;
        }
    }
}

那么,这段代码的目的是什么呢?现在,当任何代码请求检索一个客户时,它将一次性检索到一个客户及其相关的地址。


与Ids的组合

组成类型的关系也是通过使用id而不是一个类的属性来完成的。使用id而不是对象属性进行组合有几个好处。耦合被最小化,因为直接引用被移除。你可以把这些看作是简单的id关系。在下面的代码中,我们强调了订单和客户之间的组合关系,以及订单和地址之间的组合关系。这些关系是用公共属性的整数id来定义的。现在,当OrderRepository填充订单时,这些属性ID将被填充。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CRMBIZ
{
    public class Order
    {
        public Order()
        {

        }

        public Order(int orderId)
        {
            this.OrderId = orderId;
        }

        public int CustomerId { get; set; }
        public int ShippingAddressId { get; set; }

        public DateTimeOffset? OrderDate { get; set; }
        public int OrderId { get; private set; }
        public List<OrderItem> orderItems { get; set; }

        public bool Validate()
        {
            var isValid = true;

            if (OrderDate == null) isValid = false;

            return isValid;
        }
    }
}

这很好,但我们有一个问题。这些ID仅仅是简单的整数。他们实际上并没有向我们显示任何信息。订单不会有客户名称或送货地址,因为我们只使用了简单的ID。没有填充的客户对象或地址对象。由于使订单类变得如此简单,我们现在需要额外的类来显示一个订单。我们可以从一个OrderDisplay类开始。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CRMBIZ
{
    public class OrderDisplay
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public DateTimeOffset? OrderDate { get; set; }
        public List<OrderDisplayItem> OrderDisplayItemList { get; set; }
        public int OrderId { get; set; }
        public Address ShippingAddress { get; set; }
        public int ShippingAddressId { get; set; }
    }
}

这个类只包含显示一个订单所需的属性。它不引用其他类的属性,而是使用自己的属性来显示订单信息。与OrderDisplayItem类有一种构成关系,我们稍后会看一下。它需要这种关系,以便保留一个与订单相关的项目列表。OrderDisplayItem类如下所示。它只包含在订单上显示订单项目所需的属性。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CRMBIZ
{
    public class OrderDisplayItem
    {
        public int OrderItemId { get; private set; }
        public int OrderQuantity { get; set; }
        public string ProductName { get; set; }
        public decimal? PurchasePrice { get; set; }
    }
}

这段代码是如何使用的?通过给OrderRepository类添加一个新方法。注意突出显示的代码,它显示了RetrieveOrderDisplay()方法。在该方法中,代码被设置为检索OrderDisplay和OrderDisplayItem信息。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CRMBIZ
{
    public class OrderRepository
    {
        public Order Retrieve(int orderId)
        {
            Order order = new Order(orderId);

            return order;
        }

        public OrderDisplay RetrieveOrderDisplay(int orderId)
        {
            OrderDisplay orderDisplay = new OrderDisplay();

            var addressRepository = new AddressRepository();

            if (orderId == 10)
            {
                orderDisplay.FirstName = "William";
                orderDisplay.LastName = "Smith";
                orderDisplay.OrderDate = new DateTimeOffset(2017, 4, 14, 10, 00, 00, new TimeSpan(7, 0, 0));
                orderDisplay.ShippingAddress = addressRepository.Retrieve(1);
            }

            orderDisplay.OrderDisplayItemList = new List<OrderDisplayItem>();

            if (orderId == 10)
            {
                var orderDisplayItem = new OrderDisplayItem()
                {
                    ProductName = "Mouse",
                    PurchasePrice = 14.93M,
                    OrderQuantity = 2
                };
                orderDisplay.OrderDisplayItemList.Add(orderDisplayItem);

                orderDisplayItem = new OrderDisplayItem()
                {
                    ProductName = "USB Drive",
                    PurchasePrice = 8M,
                    OrderQuantity = 1
                };
                orderDisplay.OrderDisplayItemList.Add(orderDisplayItem);
            }
            return orderDisplay;
        }

        public bool Save(Order order)
        {
            return true;
        }
    }
}

继承性

在面向对象编程中,我们也有继承式的关系。在继承中,你有一个 "Is A "类型的关系。继承允许你建立一个继承其父类或基类成员的类。这允许你定义一个更具体的类的类型。因此,考虑一下客户类。我们可能有商业客户和教育客户。它们都是客户,但却是不同种类的客户。这允许代码重用,因为从父类继承成员的子类,使用的是父类中已经定义的属性和方法。

object oriented inheritance relationship

在C#中使用继承时,你只能直接从一个类中继承。然而,如果你愿意,你可以设置继承链。换句话说,你可以有一个客户类和一个继承自客户的教育类。然后,你可以建立一个学院类,它继承于教育类。


C#类之间的关系总结

在本教程中,我们看了一下C#应用程序中类之间的关系。我们看到,协作关系通常被称为 "使用A "关系。具有协作关系的类使用所需类的一个实例,并调用其属性或使用其方法。在检索或保存订单时,OrderRepository类使用Order类的一个实例,如这里的代码所示。

public Order Retrieve(int orderId)
{
    Order order = new Order(orderId);

    return order;
}

此外,AddressRepository类在检索或保存地址时使用地址类的一个实例。CustomerRepository类在检索或保存客户时使用Customer类的一个实例。所以你可以看到这种类型的关系是如何工作的。

我们还看了一下使用 "Has A "约定的组合关系。组成关系可以通过两种方式建立。第一种是通过在一个类中定义一个引用不同类的属性。在客户类中,我们定义了一个地址列表。这实际上是一个地址对象的列表。AddressList属性定义了客户类和地址类之间的组合关系。

public Customer(int customerId)
{
    this.CustomerId = customerId;
    AddressList = new List<Address>();
}

public List<Address> AddressList { get; set; }

建立组合关系的第二种方式是通过使用id。这方面的一个例子是,我们在订单类中添加了一个客户id和一个地址it。由于这些类只是使用id而不是对象引用,所以检索和保存订单数据更容易。使用这种方法,你可能需要添加额外的类来获得完整的功能,例如能够显示一个订单。最后,我们只是简单地看了一下面向对象编程中的继承,它建立了 "Is A "公约。