官术网_书友最值得收藏!

Through interfaces

Going back to our auditing example, we could have declared these properties in an interface instead. Let's see what this might look like:

using System; 
 
namespace CompanyName.ApplicationName.DataModels.Interfaces 
{ 
  public interface IAuditable 
  { 
    DateTime CreatedOn { get; set; } 
 
    User CreatedBy { get; set; } 
 
    DateTime? UpdatedOn { get; set; } 
 
    User UpdatedBy { get; set; } 
  } 
} 

Now, if a developer requires these properties, they can implement this interface as well as extending the Data Model base class:

Let's see an example of this in code now:

using System; 
using CompanyName.ApplicationName.DataModels.Interfaces; 
 
namespace CompanyName.ApplicationName.DataModels 
{ 
  public class Invoice : BaseDataModel, IAuditable 
  { 
    private DateTime createdOn; 
    private DateTime? updatedOn; 
    private User createdBy, updatedBy; 
 
    public DateTime CreatedOn 
    { 
      get { return createdOn; } 
      set { createdOn = value; NotifyPropertyChanged(); } 
    } 
 
    public User CreatedBy 
    { 
      get { return createdBy; } 
      set { createdBy = value; NotifyPropertyChanged(); } 
    } 
 
    public DateTime? UpdatedOn 
    { 
      get { return updatedOn; } 
      set { updatedOn = value; NotifyPropertyChanged(); } 
    } 
 
    public User UpdatedBy 
    { 
      get { return updatedBy; } 
      set { updatedBy = value; NotifyPropertyChanged(); } 
    } 
  } 
} 

Initially then, it seems as though this could be a better way to go, but let's continue to investigate the same scenario that we looked at with the base classes. Let's now imagine that we want to provide the same basic undo capability using interfaces. We didn't actually investigate which members would be required for this, but it will require both properties and methods.

This is where the interface approach starts to break down somewhat. We can ensure that implementers of our ISynchronization interface have particular properties and methods, but we have no control over their implementation of those methods. In order to provide the ability to undo changes, we need to provide the actual implementation of these methods, rather than just the required scaffolding.

If this was left up to the developers to implement each time they used the interface, they might not implement it correctly, or perhaps they might implement it differently in different classes and break the consistency of the application. Therefore, to implement some functionality, it seems as though we really do need to use some kind of base class.

However, we also have a third option that involves a mix of the two approaches. We could implement some functionality in a base class, but instead of deriving our Data Model classes from it, we could add a property of that type to them, so that they can still access its public members.

We could then declare an interface that simply has a single property of the type of this new base class. In this way, we would be free to add the different functionality from different base classes to just the classes that require them. Let's look at an example of this:

public interface IAuditable 
{ 
  Auditable Auditable { get; set; } 
} 

This Auditable class would have the same properties as those in the previous IAuditable interface shown in the preceding code. The new IAuditable interface would be implemented by the Data Model classes by simply declaring a property of type Auditable :

public class User : IAuditable 
{ 
  private Auditable auditable; 
 
  public Auditable Auditable 
  { 
    get { return auditable; } 
    set { auditable = value; } 
  } 
   
  ... 
} 

It could be used by the framework, for example, to output the names of each user and when they were created into a report. In the following example, we use the Interpolated Strings syntax that was introduced in C# 6.0 for constructing our string. It's like the string.Format method, but with the method call replaced with a $ sign and the numerical format items replaced with their related values:

foreach (IAuditable user in Users) 
{ 
  Report.AddLine($"Created on {user.Auditable.CreatedOn}" by
{user.Auditable.CreatedBy.Name});
}

Most interestingly, as this interface could be implemented by many different types of object, the preceding code could also be used with objects of different types. Note this slight difference:

List<IAuditable> auditableObjects = GetAuditableObjects(); 
foreach (IAuditable user in auditableObjects) 
{ 
  Report.AddLine($"Created on {user.Auditable.CreatedOn}" by
{user.Auditable.CreatedBy.Name}); }

It's worth pointing out this useful ability to work with objects of different types is not limited to interfaces. This can also be achieved just as easily with base classes. Imagine a View that enabled the end user to edit a number of different types of object.

If we added a property named PropertyChanges, that returned details of changed properties, into the BaseSynchronizableDataModel class that we will see later, in the Constructing a custom application framework section, we could use this very similar code to display a confirmation of the changes from each object back to the user:

List<BaseSynchronizableDataModel> baseDataModels = GetBaseDataModels();
foreach (BaseSynchronizableDataModel baseDataModel in baseDataModels) 
{ 
  if (baseDataModel.HasChanges)  
    FeedbackManager.Add(baseDataModel.PropertyChanges); 
} 

We have a number of choices when it comes to encapsulating pieces of pre-packaged functionality into our Data Model classes. Each of these methods that we have investigated so far have strengths and weaknesses. If we're sure that we want some pre-written functionality in every one of our Data Model classes, like that of the INotifyPropertyChanged interface, then we can simply encapsulate it in a base class and derive all of our Model classes from that.

If we just want our Models to have certain properties or methods that can be called from other parts of the framework, but are not concerned with the implementation, then we can use interfaces. If we want some combination of the two ideas, then we can implement a solution using the two methods together. It is up to us to choose the solution that best fits the requirements in hand.

主站蜘蛛池模板: 博野县| 清徐县| 石泉县| 苍山县| 潼关县| 房产| 河北省| 泸定县| 习水县| 玉溪市| 小金县| 唐山市| 大邑县| 建始县| 报价| 常山县| 西乌珠穆沁旗| 靖边县| 渝中区| 双柏县| 卢氏县| 随州市| 湘乡市| 古田县| 寿光市| 涡阳县| 大足县| 河南省| 盐山县| 乡城县| 绍兴县| 沙雅县| 新巴尔虎左旗| 淮南市| 嘉禾县| 和顺县| 本溪市| 绿春县| 修文县| 陆丰市| 永登县|