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

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.

主站蜘蛛池模板: 泽库县| 高青县| 三台县| 白朗县| 柘城县| 神农架林区| 科尔| 抚州市| 阳朔县| 石家庄市| 兴隆县| 广州市| 玉门市| 乐陵市| 钦州市| 海南省| 台州市| 开平市| 达拉特旗| 开江县| 太仓市| 怀柔区| 吴旗县| 成武县| 隆化县| 永兴县| 云龙县| 博乐市| 巴彦淖尔市| 芮城县| 浦北县| 治多县| 昔阳县| 姚安县| 崇礼县| 彭泽县| 宜黄县| 株洲市| 聂拉木县| 云龙县| 连云港市|