在过去的几个教程中,我们经历了识别几个类的过程,这些类将促进一个基本的CRM应用。在识别阶段,我们确定了业务实体,创建了属性,并确定了方法的范围。接下来,我们把责任分开,目的是为了减少耦合,使代码更简单。现在,我们进入了建立类之间关系的阶段。这些关系定义了对象如何一起工作以完成应用程序运行所需的工作。
关系的类型
在面向对象编程中,有许多类型的关系。我们要看的第一种是协作关系。在协作关系中,你经常把它称为 "使用A "关系。这是因为你可以认为一个类使用另一个类。我们要看的下一种类型的关系是组成关系。组成关系可以用 "拥有A "关系类型来指代。构成的概念是,一个对象可以由其他对象构成。一个订单有一个客户。一个订单也有一个OrderItem。我们要看的最后一种关系类型是继承关系,或者说是 "Is A "关系。一个商业客户是一个客户,或者一个消费者客户是一个客户。
协作关系
这里我们有一个类间协作类型的关系图。OrderRepository "使用 "Order对象在Retrieve中填充,在save中序列化。同样的,CustomerRepository "使用 "Customer对象和ProductRepository "使用 "Product对象。

在这段代码中,我们可以看到 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应用程序中,这种类型的关系存在于客户类和地址类之间。一个客户 "拥有一个 "地址。

在上图中,我们显示了一些组成关系。客户类 "拥有一个 "地址。订单对象也是由其他对象组成的。每个订单都有一个客户,有一个地址,有一个订单项。此外,每个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 "类型的关系。继承允许你建立一个继承其父类或基类成员的类。这允许你定义一个更具体的类的类型。因此,考虑一下客户类。我们可能有商业客户和教育客户。它们都是客户,但却是不同种类的客户。这允许代码重用,因为从父类继承成员的子类,使用的是父类中已经定义的属性和方法。

在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 "公约。