设计模式 – 工厂





5.00/5 (2投票s)
设计模式 - 工厂
到目前为止,在我们设计的模式系列中,我们已经学习了 页面对象模型 (Page Object Model) 和 外观模式 (Facade),以及它们如何在测试自动化中使用。它们都有各自的优缺点,并且都有各自更适合的场景。在第三部分,我们将学习另一个常用的设计模式,即工厂模式 (Factory design pattern)。
简单来说,工厂设计模式允许您的测试或代码确定将实例化哪个类或哪些类,通过通常以 `switch` 语句形式出现的逻辑来推迟实例化。
工厂设计模式的纯粹方法遵循 GoF (Gang of Four) 方法,该方法涉及创建四个类来实现我们的工厂。它们是:
Product
:定义工厂方法创建的对象的接口(例如,服装店首页)。ConcreteProduct
:实现 `Product` 接口(例如,女装首页、男装首页)。Creator
:声明工厂方法,该方法返回一个 `Product` 类型的对象。ConcreteCreator
:重写工厂方法以返回 `ConcreteProduct` 的实例。
那么在代码中它看起来会是什么样子呢?好吧,让我们以上面提到的在线服装店为例。您想购买的衣服类型将决定您看到的首页。我们的 `Product` 类将包含一个充当我们的 `product` 的接口,在这种情况下,是我们的首页,它将共享通用组件。我们的具体产品 (Concrete Products) 是实现我们接口的独特产品,在这种情况下,是男士首页和女士首页。
我们的 `creator` 类将包含用于确定我们将要实例化的首页类的逻辑。最后,我们的 `ConcreteCreator` 将实现我们的逻辑,以便根据传递到类内方法的参数来选择我们需要的类。
class ClothesShopHomePage
{
}
public class MensHomePage : ClothesShopHomePage
{
public void WelcomeCustomer(ClothesShopHomePage homepage)
{
}
}
public class LadiesHomePage : ClothesShopHomePage
{
public void WelcomeCustomer(ClothesShopHomePage homepage)
{
}
}
public abstract class ClothesShopFactory
{
public abstract ClothesShopHomePage GetHomepage(string homepageType);
}
public class ConcreteClothesShopFactory : ClothesShopFactory
{
public override ClothesShopHomePage GetHomepage(string homepageType)
{
ClothesShopHomePage homepage = null;
switch(homepageType)
{
case "Mens":
ClothesShopHomePage = new MensHomePage();
break;
case "Ladies":
ClothesShopHomePage = new LadiesHomePage();
break;
}
return ClothesShopHomePage;
}
}
为了节省您阅读大量代码但对您的理解没有太大帮助,我选择了一个简单的接口和实现该接口的类的示例。您可以想象我们要编写一个测试,该测试将导航到首页,并根据传递到测试的参数选择男性或女性,然后一旦我们确定了要创建的正确的首页对象,该测试将执行核心部分。
允许我们的测试实例化正确对象的核心逻辑在我们的 `ConcreteClothesShopFactory` 类中,以及 `GetHomePage` 方法(它是我们 `abstract` 类 `ClothesShopFactory` 中的 `GetHomePage` 方法的重写版本)。我理解许多阅读这篇文章的人可能是相当新的或经验不足的程序员,所以很多内容现在开始变得有点难以理解,因为涉及新的概念,如 `abstract` 类、接口和重写方法。让我们退一步。
现在,我接下来要说的可能有点奇怪。但请听我说。为了测试自动化的上下文,我想让您忽略上面我们看到的工厂设计模式的实现,并忘记我们上面看到的一些更高级的领域。不是因为它是错误的,也不是因为我们不应该这样做,恰恰相反,实际上您先看到它很好,因为它让您很好地理解了工厂模式的原理以及如何编写它。但对于测试自动化,您使用它的方式通常比这更简单。逻辑基本上是相同的,只是根据我们使用它的地方,我们不需要创建接口、创建者类和具体创建者类。
事实上,如果您阅读了我网站上的其他内容,您就会看到我们在 这篇文章 中已经将工厂方法应用到了我们的框架设计中。但如果您还没有阅读过,让我们来看看我所说的代码,它在我们 `Driver` 类中。
public static partial class Driver
{
public static TimeSpan TimeOut = new TimeSpan(0, 1, 0);
public static TimeSpan PollingInterval = new TimeSpan(0, 0, 2);
public static void SetImplicitTimeout(TimeSpan timeout)
{
DriverBase.Instance.Manage().Timeouts().ImplicitWait = timeout;
}
public static void SetImplicitTimeout()
{
SetImplicitTimeout(TimeOut);
}
public enum DriverType
{
InternetExplorer,
Chrome,
Edge
}
public static void Initialise(DriverType driverType, TimeSpan? implicitWait)
{
var initialiseTimeout = implicitWait ?? TimeOut;
try
{
switch (driverType)
{
case DriverType.InternetExplorer:
var internetExplorerOptions = new InternetExplorerOptions
{ IntroduceInstabilityByIgnoringProtectedModeSettings = true,
AcceptInsecureCertificates = true };
DriverBase.Instance = new InternetExplorerDriver
(TestContext.CurrentContext.TestDirectory, internetExplorerOptions));
break;
case DriverType.Edge:
var edgeExplorerOptions = new EdgeOptions { AcceptInsecureCertificates = true };
DriverBase.Instance =
new EdgeDriver(TestContext.CurrentContext.TestDirectory, edgeExplorerOptions));
break;
case DriverType.Chrome:
var chromeOptions = new ChromeOptions();
chromeOptions.AddArguments("test-type");
chromeOptions.AddArguments("chrome.switches", "--disable.extensions");
DriverBase.Instance = new ChromeDriver
(TestContext.CurrentContext.TestDirectory, chromeOptions, implicitWait);
break;
}
}
catch (WebDriverException)
{
throw new WebDriverException($"Failed to initialise the
{driverType.DescriptionAttribute()} web driver for Selenium");
}
DriverBase.Instance.Manage().Window.Maximize();
if(null != implicitWait)
{
SetImplicitTimeout((TimeSpan)implicitWait);
}
}
}
您可以看到,为了实例化我们想要的浏览器驱动程序,我们使用了一个简单的 `switch` 语句。然后,它会根据传递的参数实例化一个浏览器驱动程序,而这个参数来自另一个类。在某种程度上,尽管实现更简单,您可以将调用 `InitialiseDriver` 方法的方法或类视为 `Creator`,而 `InitialiseDriver` 方法本身视为 Concrete Creator。
这使用了工厂设计模式,我敢打赌,如果您以前编写过 Selenium 框架,甚至只是使用过一个,您都见过与上述示例类似的东西。这是这个设计模式的一个非常常见的用法,正如前面提到的,尽管它的实现很简单,但它仍然是正确使用它的一个很好的例子。
那么,我们应该如何将这种方法应用到我们的测试中呢?它肯定不可能仍然这么简单吧?好吧,我在这里告诉您,它甚至更简单,但同样有效。
[TestFixture]
public class MyFirstTest
{
[TestCase("Mens")
[TestCase("Ladies")]
public void UserCanNavigateToCorrectHomepage(string homepageType)
{
ClothesShopHomePage homepage = null;
switch(homepageType)
{
case "Mens":
homepage = new MensHomePage();
break;
case "Ladies":
homepage = new LadiesHomePage();
break;
}
Assert.IsTrue(homepage.IsUrlCorrect(), "Did not successfully navigate to correct homepage")
}
}
正如您所见,由于一个简单的 Nunit 属性,我们可以将不同的参数值传递给同一个测试,这意味着我们可以在同一个测试中测试我们主页的两个版本。这意味着不需要为本质上相同的测试重复代码,而是可以使用工厂设计原则根据我们正在测试的场景实例化正确的类对象。
我希望这能帮助您理解这个设计模式有多么有用,以及它实现起来有多么简单。理解它背后的理论并熟悉 GoF 方法非常重要,这样即使在使用简单的示例时,您也能欣赏到它的逻辑和结构。但如果您在阅读时觉得它有点超出您的能力范围,因为有接口、`abstract` 类等等,那么不用担心,因为在测试自动化的上下文中,它们并不一定是必需的。
最后一点,我想说的是,尽管它可能非常有效,但重要的是不要过度使用它。如果不在正确的场景中使用,它可能会给您的代码带来不必要的复杂性,所以请确保这是一个使用它的好时机。一些好的使用时机包括:
- 测试需要弄清楚应该创建哪些对象。
- `Parent` 类允许后续实例化到子类,这意味着对象的创建是在需要时完成的。
- 一个类(`creator`)不知道它需要创建哪些类。
这篇文章 设计模式 – 工厂模式 最先出现在 Learn Automation。