簡體   English   中英

我應該如何編寫我的工廠類 - 生成派生對象

[英]How should I write my Factory Class - Generates derived objects

首先,我將列出我的一般設置並描述課程的目標,因為我覺得它對這個問題很重要:

  • 這旨在根據應用程序的需要動態緩存文件和文件夾。
  • 如果用戶需要將它們復制到某個地方,這允許他們復制本地版本,而不是每次都從遠程下載。 相反,它只會在文件/文件夾的更新可用時下載。 (對於文件夾,這是通過 robocopy 鏡像執行的)
  • 另外,對不起這里的文字牆
public enum SyncMode
{
     None = -2, // SyncMode Not Defined by Object (don't actually intend in my use case, added for future compatibility/interfaces)
     AlwaysReturnNetwork = -1, //DynamicPath should prioritize the network string, UNLESS the network is unavailable and the path exists locally.
     Dynamic = 0,   // Another one for potential future objects
     OfflineCache = 1, // Allow file/folder to be downloaded for use in offline mode of the application
     LocalCache = 2,   // Cache the file/folder locally for quicker access
     AlwaysReturnLocal = 3, // Designed to essentially be a file that is required by the application
}

// this class will allow consumers to override some functions, for example the actions to take when downloading the file from remote to local. 
//(base will assume its not web-based, so a web-based consumer will need to implement that functionality by overriding the virtual methods)
// As shown below, the consumer must also specify how to determine if the user is set up to local/offline cache, as well as if the application is running in offline (assume network unavailable) mode
public abstract class FileOperations 
{
   abstract bool IsApplicationOffline {get;}
   abstract bool AllowOfflineCaching {get;}
   abstract bool AllowLocalCaching {get;}
}

public abstract class AbstractNetworkPath
{
    public string LocalPath { get; } // File/Folder might exist at this location on the pc
    public string NetPath { get; } // File/Folder should exist at this location on a remote location
    public SyncMode SyncMode {get;} //Determine how the DynamicPath functions
    public string DynamicPath {get;} //String is returned based on the SyncMode value and if the path exists locally
    protected bool ShouldCache => SyncMode == AlwaysReturnLocal || SyncMode >=OfflineCache && FileOps.AllowOfflineCaching || SyncMode >= LocalCaching && FileOps.AllowLocalCaching;
    internal protected FileOperations FileOps {get;} // Reference to the object passed into the ctor/factory
}

public class SyncedFile : AbstractNetworkPath
{     
     public void CopyTo(string destPath)
     {
          if (ShouldCache) this.FileOps.UpdateFile(this.NetPath, this.LocalPath);
          FileOps.CopyFile(this.DynamicPath, destPath);
     }  
}

public class NetworkFolder: AbstractNetworkPath
{     
     // This class represents a remote folder, and prioritizes the network location
     // This is meant to specify some location, not necessarily one that gets downloaded.
     // For example, a main directory with a bunch of files/folders, most of which the application doesn't need or want.
}  


public class SyncedFolder : NetworkFolder
{
     // This class represents a remote folder, but prioritizes the local location
     // This class also adds the methods to download the folder locally

     public void CopyTo(string destPath)
     {
          if (ShouldCache) this.FileOps.UpdateFolder(this.NetPath, this.LocalPath);
          FileOps.CopyFolder(this.DynamicPath, destPath);
     }  
}  

所以這就是有趣的地方:

  • 我要為此設置的工廠將包含 FileOps 對象,這樣消費者就不必在構造函數中不斷地指定它——工廠將為他們做這件事。
  • SyncedFile 存在於 NetworkFolder 中(但不一定在該文件夾中,但可能在子文件夾路徑中),因此 SyncedFileFactory 必須包含使用該父級創建對象的方法。 同樣的原則也適用於 SyncedFolder
  • SyncedFile 和 SyncedFolder 都希望從不使用“AlwaysReturnNetwork”枚舉值。 這就是我想使用工廠方法的部分原因,以確保永遠不會將值傳遞給 CTOR,因為這些對象處於該狀態是沒有意義的。
  • 還存在一個我允許從中創建對象的接口。 這樣,消費者可以從數據庫(或者在我的情況下是一個 excel 文件)中將數據讀取到實現接口的某個對象中,然后可以將其傳遞到工廠以構造對象。

以下是問題:

  • 我的計划是擁有一個SyncedFileFactory 、一個NetworkFolderFactory和一個SyncedFolderFactory

    • 但是,既然我已經把它寫出來了,我可以評估 SyncMode 並從 NetworkFolderFactory 返回一個 SyncedFolder 或 NetworkFolder。 這是否容易理解,或者我應該擁有自己的工廠以清晰地構建?
    • 請注意,由於以下問題,我最初沒有想到這一點:
  • 擁有一個將其 SyncMode 應用於正在構造的類的工廠是否有意義? 還是一個更通用的工廠,它只是驗證傳遞給方法的 SyncMode?

這是我目前所擁有的:

public class SyncedFileFactory 
{
     Public SyncedFileFactory(SyncMode syncmode, FileOperations fileOps) { /* Ctor*/ }
     public FileOperations FileOperationsObj{get;}
     Public SyncMode SyncMode {get;}
}

public class NSOFactory
{
     Public NSOFactory(FileOperations fileOps)
     {
          FileFactory_Offline  = new(SyncMode.OfflineCache, fileOps);
          FileFactory_LocalCache = new(SyncMode.LocalCache, fileOps);
          FileFactory_Required = new(SyncMode.AlwaysReturnLocal, fileOps);
     }

     public SyncedFileFactory FileFactory_Offline {get;}
     public SyncedFileFactory FileFactory_LocalCache {get;}
     public SyncedFileFactory FileFactory_Required {get;}
}

我喜歡這強制 SyncModes 的類型,並且只構造具有有效同步模式的對象,但是當從已經指定了 syncMode 的對象構造時,我們遇到了一些問題,我不確定解決這個問題的最佳方法同時保持工廠結構清晰

  • 使用接口(強制指定 SyncMode)時,不清楚應該發生什么。 (是使用接口指定的SyncMode,還是工廠的SyncMode?)
  • 使用父 NetworkFolder 對象生成它時的相同問題。
    • 但是如果 NetworkFolder 對象是“AlwaysReturnNetwork”,那么 SyncedFile 和 SyncedFolder 無論如何都不應該繼承它

編輯:可能的解決方案 - 需要考慮這個:

所以現在我的思路基本上是:

  • OfflineCache應該優先於LocalCache ,用於構造,但AlwaysReturnLocal是構造的最高優先級。 那么我是否只是評估輸入父級和選擇的工廠並采取相應的行動? 這可能是最容易做到的事情。 但是如果有人去看它,你最終會得到與輸入值不同的輸出值。 這種交互降低了清晰度,但與庫的預期功能保持一致。

好的,所以當我繼續開發這個 dll 時,我遇到了一些其他問題,這些問題已經解決了。 對於可能受到啟發的其他人,我將在下面詳細說明我的解決方案。

原始思維過程:創建每個 SyncMode 類型的工廠(至少是主要值)。 我想到想要它的原因:

  • 有些類型沒有意義/不能有某些值。 示例:SyncedFile 絕不應該是“AlwaysReturnNetwork”。 該類的全部意義在於它獲取了一個本地緩存的文件,因此允許從不返回本地路徑是沒有意義的。

這方面的問題:

  • 需要實例化的工廠對象太多
  • 某些方法默認繼承該值,而不是使用工廠特定的值。 (創建駐留在 SyncedFolder 中的 SyncedFile 會繼承 SyncedFolder 的枚舉值)

原始計划:有一個庫引用的全局設置對象負責定義Func<bool>目標,以確定應用程序是否處於離線模式,是否允許本地緩存等。問題:

  • 像這樣的全局定義對象對於獨立的應用程序來說是很好的。 但是為了更好和更靈活,如果需要的話,函數目標應該能夠為每個對象定制。

解決方案:

  • 將用於全局設置的靜態類轉換為對象類。 這允許應用程序在需要時具有單個設置對象,或者在需要時具有多個設置對象。
  • 工廠對象在其構造函數中采用新的 NSOFactorySettings 對象。 (基類現在具有NSOFactorySettings類型的屬性。所有派生類現在都指向此屬性,而不是靜態類。)
  • 工廠方法已被重寫為更通用,接受枚舉作為參數,而不是依賴於工廠的枚舉值。 (可能 6 家工廠減少到 1 家。此外,這次更改的結果是,工廠代碼更加簡潔明了。)
  • 為了強制將枚舉類型傳遞給構造函數,我創建了一個新的 Struct 類型,其基礎值是枚舉類型。 然后只允許靜態引用它。

用於強制執行特定枚舉的結構:

/// <summary>
    /// Subset of <see cref="SyncMode"/> values that are valid for <see cref="SyncedFile"/> and <see cref="SyncedFolder"/> objects.
    /// </summary>
    [ImmutableObject(true)]
    public readonly struct CacheMode
    {
        private CacheMode(SyncMode mode) { Mode = mode; }

        public static readonly CacheMode Dynamic = new(SyncMode.Dynamic);

        public static readonly CacheMode DynamicOfflineCached = new(SyncMode.DynamicOfflineCached);

        public static readonly CacheMode DynamicCached = new(SyncMode.DynamicCached);

        public static readonly CacheMode AlwaysReturnLocal = new(SyncMode.AlwaysReturnLocal);

        public readonly SyncMode Mode;

        public static implicit operator SyncMode(CacheMode value) => value.Mode;

        public static explicit operator CacheMode(SyncMode value) => FromSyncMode(value);
        
        public static CacheMode FromSyncMode(SyncMode value)
        {
            return value switch
            {
                SyncMode.AlwaysReturnLocal => CacheMode.AlwaysReturnLocal,
                SyncMode.DynamicCached => CacheMode.DynamicCached,
                SyncMode.DynamicOfflineCached => CacheMode.DynamicOfflineCached,
                SyncMode.Dynamic => CacheMode.Dynamic,
                _ => throw new InvalidCastException("SyncMode value must be Dynamic or Greater. Value passed into method: " + Enum.GetName(typeof(SyncMode), value))
            };
        }

        public static CacheMode DefaultOrGreater(SyncMode? syncMode)
        {
            if (syncMode is null || syncMode == SyncMode.Dynamic) return CacheMode.Dynamic;
            return FromSyncMode(Factory.NSOFactoryBase.PrioritizeSyncMode(SyncMode.Dynamic, (SyncMode)syncMode));
        }

        public static CacheMode DefaultOrGreater(SyncMode? syncMode, Interfaces.INetworkSyncObject networkSyncObject)
        {
            if (syncMode is null) return DefaultOrGreater(networkSyncObject?.SyncPriority);
            var mode = NSOFactoryBase.PrioritizeSyncMode((SyncMode)syncMode, networkSyncObject?.SyncPriority ?? SyncMode.Dynamic);
            return DefaultOrGreater(mode);
        }
    }

工廠類:

namespace NetworkSyncObjects
{
    public partial class SyncedFile
    {

        /// <summary>
        /// Factory to create <see cref="SyncedFile"/> objects. <br/>
        /// This factory does not specify the <see cref="CacheMode"/>.
        /// </summary>
        public class SyncedFileFactory : NSOFactoryBase
        {

            #region < Factory Construction >
            internal protected SyncedFileFactory(NSOFactorySettings factorySettings) : base(factorySettings) { }

            public static SyncedFileFactory CreateFactory(NSOFactorySettings factorySettings)
            {
                //Validate Input
                if (factorySettings is null) throw new ArgumentNullException(nameof(factorySettings));
                return new SyncedFileFactory(factorySettings);
            }

            public static SyncedFileFactory CreateFactory(NSOFactorySettings objectToClone, string rootFolder)
            {
                var fS = new NSOFactorySettings(rootFolder, objectToClone);
                return new SyncedFileFactory(fS);
            }

            #endregion

            static bool PathValidation(string path, string variableName, out Exception e)
            {
                if (String.IsNullOrWhiteSpace(path))
                {
                    e = new ArgumentException($"Invalid Argument: '{variableName}' is null or empty", variableName);
                    return true;
                }
                if (!PathEx.IsPathFullyQualified(path))
                {
                    e = new ArgumentException($"Invalid Argument: '{variableName}' is not fully qualified! \nSupplied path: {path}", variableName);
                    return true;
                }
                if (!Path.HasExtension(path))
                {
                    e = new ArgumentException($"Invalid Argument: '{variableName}' does not have a file extension! \nSupplied path: {path}", variableName);
                    return true;
                }
                if (string.IsNullOrWhiteSpace(Path.GetFileName(path)))
                {
                    e = new ArgumentException($"Unable to retrieve filename from SourceFilePath! \n Supplied path: {path}", variableName);
                    return true;
                }
                e = null;
                return false;
            }

            #region < Factory Methods >

            #region < From Source and Destination FilePath >

            public SyncedFile FromSource(string SourceFilePath, CacheMode syncMode)
            {
                if (PathValidation(SourceFilePath, nameof(SourceFilePath), out var E)) throw E;
                string localPath = ConvertNetworkPathToLocalPath(SourceFilePath, null);
                return FromSourceAndDestination(SourceFilePath, localPath, syncMode);
            }
            public SyncedFile FromSourceAndDestination(string SourceFilePath, string DestinationFilePath, CacheMode syncMode, string ID = null)
            {
                if (PathValidation(SourceFilePath, nameof(SourceFilePath), out var e)) throw e;
                if (PathValidation(DestinationFilePath, nameof(DestinationFilePath), out e)) throw e;
                return new SyncedFile(
                    networkPath: SourceFilePath,
                    localPath: DestinationFilePath,
                    iD: ID ?? "SyncedFile_" + Path.GetFileName(DestinationFilePath),
                    syncMode: syncMode,
                    this.FactorySettings
                    );
            }

            #region < Overloads >

            public SyncedFile FromSourceAndDestination(string SourceFilePath, DirectoryInfo DestinationFolder, CacheMode syncMode) => FromSourceAndDestination(SourceFilePath, Path.Combine(DestinationFolder.FullName, Path.GetFileName(SourceFilePath)), syncMode);

            
            #endregion

            #region < From NetworkFolder >

            /// <inheritdoc cref="SyncedFile.SyncedFile(string, INetworkFolder, CacheMode?)"/>
            public SyncedFile FromNetworkFolder(string filePath, INetworkFolder parent, string iD = null) => new SyncedFile(filePath, parent, CacheMode.DefaultOrGreater(parent.SyncPriority));

            #endregion

            #region < From INetworkSyncDataObject >

      
            public SyncedFile FromIConstructionData(IConstructionData_NetPathOnly dataObject)
            {
                if (dataObject is null) throw new ArgumentNullException(nameof(dataObject));
                if (PathValidation(dataObject.NetworkPath, "dataObject.NetworkPath", out Exception e)) throw e;
                string netPath = FactorySettings.GetUncPath(dataObject.NetworkPath, out MappedDrive drv);
                string localPath = ConvertNetworkPathToLocalPath(dataObject.NetworkPath, drv);

                return new SyncedFile(
                    netPath,
                    localPath,
                    dataObject.ID,
                    CacheMode.DefaultOrGreater(dataObject.SyncMode),
                    this.FactorySettings);
            }

 
            public SyncedFile FromIConstructionData(IConstructionData dataObject)
            {
                if (dataObject is null) throw new ArgumentNullException(nameof(dataObject));
                if (PathValidation(dataObject.NetworkPath, "dataObject.NetworkPath", out Exception e)) throw e;
                if (PathValidation(dataObject.LocalPath, "dataObject.LocalPath", out e)) throw e;

                return new SyncedFile(
                    dataObject.NetworkPath,
                    dataObject.LocalPath,
                    dataObject.ID,
                    CacheMode.DefaultOrGreater(dataObject.SyncMode),
                    this.FactorySettings
                    );
            }

            #endregion

            #endregion
        }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM