使用策略模式(C# 示例)先决条件示例问题输入策略模式。踩刹车。

2025-06-10

使用策略模式(C# 中的示例)

先决条件

示例问题

输入策略模式。

踩刹车。

先决条件

为了更好地理解这篇文章,如果您对面向对象编程和继承有基本的了解,并且掌握了 C# 或 Java 等面向对象编程语言,将会对您有所帮助。我希望即使您不是 C# 语法专家,也能理解策略模式背后的主要思想。

示例问题

我们正在开发一个保持文件同步的应用程序,初始需求是,如果目标目录中存在某个文件,但源目录中不存在,则应删除该文件。我们可能会这样写


public void Sync(Directory source, Directory destination)
{
    CopyFiles(source, destination)
    CleanupFiles(source, destination)
}

private void CleanupFiles(Directory source, Directory destination)
{
    foreach(var destinationSubDirectory in destination.SubDirectories)
    {
        var sourceSubDirectory = source.GetEquivalent(destinationSubDirectory);
        if(sourceSubDirectory != null)
        {
            CleanupFiles(sourceSubDirectory, destinationSubDirectory);
        }
        else
        {
            // The source sub directory doesn't exist
            // So we delete the destination sub directory
            destinationSubDirectory.Delete();
        }
    }

    // Delete top level files in this directory
    foreach(var file in destination.Files)
    {
        if(source.Contains(file) == false)
        {
            file.Delete();
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

嘿,它们看起来真棒!我们使用了递归,而且可读性很高。但是,就像所有软件项目一样,需求也会发生变化。我们发现有时候我们不想删除多余的文件。这其实是个非常快速的解决方法。我们可以通过勾选一个标志来解决这个问题。

public void Sync(Directory source, Directory destination)
{
    CopyFiles(source, destination)
    if(_shouldDeleteExtraFiles)
    {
        CleanupFiles(source, destination)
    }
}
Enter fullscreen mode Exit fullscreen mode

因为我们将删除逻辑分离到了它自己的方法中,所以一个简单的 If 语句就可以完成任务。直到需求再次发生变化。现在,我们希望应用程序允许用户选择是否保留扩展名为 .usf 的文件。这需要我们对 CleanupFiles 方法进行一些修改。

private void CleanupFiles(Directory source, Directory destination)
{
    foreach(var DestinationSubDirectory in destination.SubDirectories)
    {
        var sourceSubDirectory = source.GetEquivalent(DestinationSubDirectory);
        if(sourceSubDirectory != null)
        {
            CleanupFiles(sourceSubDirectory, DestinationSubDirectory);
        }
        else
        {
            // The source sub directory doesn't exist
            // So we delete the destination sub directory
            destinationSubDirectory.Delete();
        }
    }

    // Delete top level files in this directory
    foreach(var file in destination.Files)
    {
        if(_keepUSF && file.HasExtension("usf"))
        {
            continue;
        }

        if(source.Contains(file) == false)
        { 
            file.Delete();
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

嗯,现在我们添加了几个全局标志,并且依赖它们的逻辑分布在多个方法中。(除了本文讨论的范围之外,我们可能还需要检查源目录中不存在的目标目录是否包含扩展名为 .usf 的文件。)清理这段代码确实对我们大有裨益。我们应该如何分离逻辑,才能避免决策使用哪种删除逻辑和逻辑本身交织在一起?

输入策略模式。

来自 w3sDesign.com:

策略模式(也称为策略模式)是一种行为型软件设计模式,允许在运行时选择算法。代码不是直接实现单个算法,而是接收运行时指令,决定使用一系列算法中的哪一个。

现在我知道我一直在谈论清理代码的必要性,而不是在运行时选择算法。由于使用了标志,我们已经能够在运行时更改算法,但我们将会看到清理的好处。

首先,让我们编写一个界面代码。

public interface IDeleteStrategy
{
    void DeleteExtraFiles(Directory source, Directory Destination);
}

Enter fullscreen mode Exit fullscreen mode

为了简化,假设客户端将其传递到我们的方法中,因此我们像这样更新它的签名和内容:

public void Sync(
    Directory source,
    Directory destination,
    IDeleteStrategy deleteStrategy) //Expect to recieve a delete strategy
{
    CopyFiles(source, destination)

    //Call our delete strategy and let it handle the clean up
    deleteStrategy.DeleteExtraFiles(source, destination)
}
Enter fullscreen mode Exit fullscreen mode

嗯,这确实清理了一些东西。现在我们来看看一些实现。首先是一个简单的选项,不删除任何内容。如果传入此策略,方法调用将不执行任何操作,目标目录中的任何多余文件都将保留。

public class NoDelete : IDeleteStrategy
{
    public void DeleteExtraFiles(Directory source, Directory Destination)
    {
        //Do nothing!
    }
}
Enter fullscreen mode Exit fullscreen mode

现在,我们可以编写两种不同的策略来分别删除不在源文件中的文件和保留 USF 文件,但保留 USF 文件的策略是删除不在源文件中文件的策略的扩展。因此,我们可以通过继承来节省一些代码。

以下是我们的策略:如果文件不在源目录中,则删除目标目录中的文件。请注意,我们保留了之前的递归操作来清理子目录,但我们将删除顶级文件以及判断是否删除文件的逻辑拆分开来。

public class DeleteIfNotInSource : IDeleteStrategy
{
    public void DeleteExtraFiles(Directory source, Directory Destination)
    {
        foreach(var destinationSubDirectory in destination.SubDirectories)
        {
            var sourceSubDirectory = source.GetEquivalent(destinationSubDirectory);
            if(sourceSubDirectory != null)
            {
                // use recursion to pick up sub directories
                DeleteExtraFiles(sourceSubDirectory, destinationSubDirectory);
            }
            else
            {
                // The source sub directory doesn't exist
                // So we delete the destination sub directory
                destinationSubDirectory.Delete();
            }
        }

        DeleteTopLevelFiles(source, destination);
    }

    private void DeleteTopLevelFiles(Directory source, Directory destination)
    {
        foreach(var file in destination.Files)
        {
            if(ShouldDelete(file, source))
            { 
                file.Delete();
            }
        }
    }

    protected bool ShouldDelete(File file, Directory source)
    {
        return source.Contains(file) == false;
    }
}
Enter fullscreen mode Exit fullscreen mode

所以现在我们实际上只需要通过继承 DeleteIfNotInSource 并重写 ShouldDelete 方法来实现保留 usf 文件的策略!

public class KeepUSF : DeleteIfNotInSource
{
    public override bool ShouldDelete(File file, Directory source)
    {
        if(file.HasExtension("usf"))
        {
            return false;
        }

        //Defer to our base class
        return base.ShouldDelete(file, source);
    }
}
Enter fullscreen mode Exit fullscreen mode

所以现在如果我们需要添加新的策略,我们不需要修改任何其他代码。我们可以创建一个实现 IDeleteStrategy 接口的新类,然后只需添加选择该策略的逻辑即可。

例如,我们可以使用类似配置设置之类的东西来选择策略,然后将其传递给同步方法。以下是一个例子(如果你想进一步了解,可以使用工厂模式)。

// The application configuration tells us what delete strategy we are using
var deleteStrategyFactory = DeleteStrategyFactory.CreateFactory(configSettings);
...

// We don't know exactly what strategy we're getting, 
// better yet we don't care!
var deleteStrategy = deleteStrategyFactory.getDeleteStrategy();
SyncProcess.Sync(source, destination, deleteStrategy);

Enter fullscreen mode Exit fullscreen mode

踩刹车。

一些熟悉设计模式的人可能会说“嘿,Sam,你为什么不使用装饰器模式来实现 Keep USF 功能!?”(如果你愿意的话,可以阅读更多内容。)

有时候,最好不要仅仅因为可以就应用某种设计模式。事实上,这个例子只是为了教学目的而保持简单。有人可能会说,这里的策略模式有点矫枉过正。

务必权衡应用设计模式的收益与成本。此处应用策略模式为我们的项目添加了一个接口和三个新类。换句话说:“确保收益值得付出。”

鏂囩珷鏉ユ簮锛�https://dev.to/sam_ferree/using-the-strategy-pattern-examples-in-c-4jn6
PREV
使用 ESLint 和 Prettier 为 Visual Studio Code 设置 TypeScript
NEXT
面向初学者的 Golang Restful CRUD