首页 > 解决方案 > 用于创建对象并在其他方法中填充其属性的模式

问题描述

我创建了一个 Photo 类,其中包含许多属性,例如大小、文件名、纬度/经度、位置等。

public class Photo {
    public string FileName { get; set; }
    public string Title { get; set; }
    public double Size { get; set; }
    public DateTime CapturedDate { get; set; }
    public string Latitude { get; set; }
    public string Longitude { get; set; }
    public string LocationName { get; set; }
    public byte[] Data { get; set; }
}

我想创建此类的一个对象并逐步填充其属性,其中每个步骤都需要在设置对象的某些值之前执行某种操作。

在一个快速而肮脏的解决方案中,我只是创建了一些 void 方法来填充对象属性,但我认为这是反模式。

var photo = new Photo();
photo.FileName = "Photo 1"
SetExifData(photo, photoStream);
SetLocationName(photo);
DoSomethingElse(photo);

void SetExifData(Photo photo, Stream photoStream) {
   //Read exit data from the photo stream and update the object
   photo.Latitude = "10,0";
   photo.Longitude = "10,0";
   photo.CapturedDate = ....
}
void SetLocationName(Photo photo){
   //Call external API to get location name from lat/lng
   photo.LocationName = "London"
}
void DoSomethingElse(Photo photo){
}

最好将此对象传递给某种管道或构建器。哪种模式适合这种情况?

标签: c#design-patterns

解决方案


仅根据您告诉我的内容,构建器模式可能是要走的路。可以创建它以支持流畅的接口(如果足以满足您的 Pipeline 需求),或者作为设置更专业的 Pipeline 模式时使用的中间步骤。

class PhotoBuilder
{
   // All properties goes here
   private string Latitude { get; set; }
   private string Longitude { get; set; }
   ...

   public PhotoBuilder WithExifDataFromStream(Stream photoStream)
   {
      Latitude = ...;
      Longitude = ...;
      ...
      return this;
   }

   public Photo Build() => return new Photo(...);
}

我建议隐藏构建器中的所有状态,并且只在最后将对象创建为只读对象。它使推理更简单。

稍微不那么有吸引力的替代方法是将您的设置器隐藏为内部并从构建器中使用它们。出于纯度原因,我倾向于尽可能远离此类内部结构,但 YMMV。它当然有助于非常大的结构。即使使用这种方法,我也会敦促您只在最后公开对象以保持构建器和对象本身的分离。还要确保传递一个“不可变”对象,IEBuild会在调用后立即清除对该照片的所有引用,以避免在构建后无意中更改对象。

空闲的沉思:

我发现对你在这样一个构建器中开始使用的逻辑类型进行相当限制是很好的。当然,这将取决于您的用例,但在较大的系统中,某些逻辑通常会在许多地方使用,而且我不止一次为从一个不如它干净的界面重构出多用途功能而头疼可能。

在这种情况下,这可以转换为SetLocation(double longitude, double latitude, string locationName)and WithCaptureDate(...)。这将使构建器能够在更多场景中使用,并允许您稍后在需要时在其之上创建特殊功能。

我发现它也有助于单元测试和组合。例如,您没有将构建器绑定到 Location API。当然,这也可以通过依赖注入来解决(ILocationResolver或类似的),但是您可能正在查看一个使单一责任原则无效的对象。

最后,它非常依赖于您正在构建的系统和这个特定类的要求。我会错误地创建另一个包装器来执行所有位置和流解析的东西,但这是来自那些主要处理功能重用和可组合性要求很高的大型互连系统的人。如果系统非常小,这可能只会引入不必要的复杂性。


推荐阅读