web-dev-qa-db-ja.com

Nライブラリを使用して特定の動作を定義するための共通インターフェースを作成する

私はPuppeteer(クロムを自動化)とSelenium(すべての主要ブラウザーを自動化)を使用できるブラウザー自動化ライブラリーを作成していますが、将来的にはさらに多くのライブラリーを追加できるようにすることも目標です。

使用可能ないくつかのブラウザーを定義するEnumがあります

_public enum ExternalBrowserType
{
    ChromeSelenium,
    FirefoxSelenium,
    ChromiumPuppeteer
}
_

また、インスタンス化でき、ユーザーが必要なブラウザーをユーザーが制御できるようにするExternalBrowserクラス。

_public class ExternalBrowser
{
    public ExternalBrowserType Type { get; private set; }
    public ExternalBrowserMouse Mouse { get; set; }

    public ExternalBrowser(ExternalBrowserType externalBrowserType)
    {
        Type = externalBrowserType;
    }
}
_

ここで問題が発生します。明らかに、これらのライブラリには、たとえばブラウザ内でマウスを動かすためのさまざまなAPIがあります。

  • セレン:new Actions(IWebDriver).MoveByOffset(x, y);
  • 操り人形師:await Page.Mouse.MoveAsync(x, y);

ExternalBrowserMouseという抽象クラスを作成することで解決できます。

_public abstract class ExternalBrowserMouse : ExternalBrowserModule
{
    public Point CurrentPosition;

    public ExternalBrowserMouse(ExternalBrowser externalBrowser) : base(externalBrowser)
    {
    }

    public abstract void MoveByOffset(int x, int y);
}
_

ExternalBrowserModule:

_public class ExternalBrowserModule
{
    protected ExternalBrowser ExternalBrowser;
    protected object BrowserController;

    public ExternalBrowserModule(ExternalBrowser externalBrowser)
    {
        ExternalBrowser = externalBrowser;
    }
}
_

そして、この基本クラスから派生して、PuppeteerとSeleniumのインプリメンターを構築します。

次に、列挙値に基づいて、ユーザーが選択したブラウザのバージョンと一致する適切なクラスをインスタンス化する必要があります。

しかし、継承することで、ExternalBrowserMouseの新しい動作を定義したいとします。たとえば、定義されたパスを使用して、より人間的な方法で移動し、デフォルトのようにすぐに移動したくない場合、どうすればよいでしょうか。

1
Joao Vitor

ここにコマンドパターンが登場します。このパターンを使用すると、各ブラウザが実装する必要のあるアクションを分離し、新しいAPIを将来実装する柔軟性を持たせることができます。これにより、アクションにバリエーションを実装することもできます。 MoveMouseおよびMoveMouseLikeHumanのコマンドを使用できます。

Pointクラスはusing System.Drawing;を介して参照されます

まず、インターフェースと基本クラスを定義します。

// The receiver interface defines the contract for receivers implemented
// by each external browser. Each one will need to implement this
// interface. Additional actions are added here and in turn implemented 
// by external browser receiver implementations
public interface IBrowserReceiver
{
    void MoveMouse(Point position);
    void ClickButton(Point position);
    void Delay(int delaySeconds);

    // additional external browser actions defined here
    // ...
}

// the command base is implemented by each command action
public abstract class BrowserCommandBase
{
    protected readonly IBrowserReceiver Receiver;
    protected BrowserCommandBase(IBrowserReceiver receiver)
    {
        this.Receiver = receiver ?? throw new ArgumentNullException(nameof(receiver));
    }

    public abstract void Execute();
}

// this class is implemented by each external browser implementation and is used
// to queue multiple commands and invoke them sequentially 
public abstract class ExternalBrowserBase
{
    private readonly Queue<BrowserCommandBase> commandQueue;
    public ExternalBrowserType Type { get; }
    protected ExternalBrowserBase(ExternalBrowserType type)
    {
        this.Type = type;
        commandQueue = new Queue<BrowserCommandBase>();
    }
    public void QueueCommand(BrowserCommandBase command)
    {
        commandQueue.Enqueue(command);
    }
    public void ExecuteCommands()
    {
        foreach (BrowserCommandBase command in commandQueue)
        {
            command.Execute();
        }
    }
    public void ClearCommands()
    {
        commandQueue.Clear();
    }
}

…そしてあなたの列挙型:

public enum ExternalBrowserType
{
    ChromeSelenium,
    FirefoxSelenium,
    ChromiumPuppeteer
}

次に、レシーバーの実装を定義します。これらのクラスは特定のブラウザーのレシーバーインターフェイスを実装し、各ブラウザーが特定のAPIに必要なアクションを実装できるようにします。つまり、MoveMouseは、Selenium APIとPuppeteer APIでは異なる方法で実装されます。

public class ChromeSeleniumReceiver : IBrowserReceiver
{
    public void MoveMouse(Point position)
    {
        Console.WriteLine($"{nameof(ChromeSeleniumReceiver)} : {nameof(MoveMouse)}; {position.ToString()}");
    }
    public void ClickButton(Point position)
    {
        Console.WriteLine($"{nameof(ChromeSeleniumReceiver)} : {nameof(ClickButton)}; {position.ToString()}");
    }
    public void Delay(int delaySeconds)
    {
        Console.WriteLine($"{nameof(ChromeSeleniumReceiver)} : {nameof(Delay)}; delaying for {delaySeconds.ToString()} second(s)");
    }
}

public class ChromiumPuppeteerReceiver : IBrowserReceiver
{
    public void MoveMouse(Point position)
    {
        Console.WriteLine($"{nameof(ChromiumPuppeteerReceiver)} : {nameof(MoveMouse)}; {position.ToString()}");
    }
    public void ClickButton(Point position)
    {
        Console.WriteLine($"{nameof(ChromiumPuppeteerReceiver)} : {nameof(ClickButton)}; {position.ToString()}");
    }
    public void Delay(int delaySeconds)
    {
        Console.WriteLine($"{nameof(ChromiumPuppeteerReceiver)} : {nameof(Delay)}; delaying for {delaySeconds.ToString()} second(s)");
    }
}

public class FirefoxSeleniumReceiver : IBrowserReceiver
{
    public void MoveMouse(Point position)
    {
        Console.WriteLine($"{nameof(FirefoxSeleniumReceiver)} : {nameof(MoveMouse)}; {position.ToString()}");
    }
    public void ClickButton(Point position)
    {
        Console.WriteLine($"{nameof(FirefoxSeleniumReceiver)} : {nameof(ClickButton)}; {position.ToString()}");
    }
    public void Delay(int delaySeconds)
    {
        Console.WriteLine($"{nameof(FirefoxSeleniumReceiver)} : {nameof(Delay)}; delaying for {delaySeconds.ToString()} second(s)");
    }
}

コマンドの実装:

コマンドは、各外部ブラウザによって実装されたReceiverを呼び出します。

public class MoveMouseCommand : BrowserCommandBase
{
    private readonly Point position;
    public MoveMouseCommand(IBrowserReceiver receiver, Point position)
        : base(receiver)
    {
        this.position = position;
    }
    public override void Execute()
    {
        this.Receiver.MoveMouse(position);
    }
}

// moves the mouse gradually from the start point to the end point by
// repeatedly calling the move mouse command until the end point is reached
public class MoveMouseLikeHumanCommand : BrowserCommandBase
{
    private readonly Point fromPosition;
    private readonly Point toPosition;
    public MoveMouseLikeHumanCommand(IBrowserReceiver receiver, 
        Point fromPosition, Point toPosition)
        : base(receiver)
    {
        this.fromPosition = fromPosition;
        this.toPosition = toPosition;
    }
    public override void Execute()
    {
        Point startPosition = fromPosition;
        Point lastPosition = toPosition;
        Point currentPosition = startPosition;
        while (currentPosition != lastPosition)
        {
            this.Receiver.MoveMouse(currentPosition); // call move mouse command
            currentPosition = GetNextMovePoint(startPosition, lastPosition);
            startPosition = currentPosition;
        }
        this.Receiver.MoveMouse(lastPosition);
    }

    private Point GetNextMovePoint(Point start, Point end)
    {
        int newX = GetNextMoveValue(start.X, end.X);
        int newY = GetNextMoveValue(start.Y, end.Y);
        return new Point(newX, newY);
    }

    private static int GetNextMoveValue(int start, int end)
    {
        if (start < end)
        {
            return start + 1;
        }
        if (start > end)
        {
            return start - 1;
        }
        return start;
    }
}

public class ClickButtonCommand : BrowserCommandBase
{
    private readonly Point position;
    public ClickButtonCommand(IBrowserReceiver receiver, Point position) 
        : base(receiver)
    {
        this.position = position;
    }
    public override void Execute()
    {
        this.Receiver.ClickButton(this.position);
    }
}

public class DelayCommand : BrowserCommandBase
{
    private readonly int delaySeconds;
    public DelayCommand(IBrowserReceiver receiver, int delaySeconds) : base(receiver)
    {
        this.delaySeconds = delaySeconds;
    }

    public override void Execute()
    {
        base.Receiver.Delay(this.delaySeconds);
    }
}

最後に、ExternalBrowserBaseクラスから継承する外部ブラウザーを実装します。

public class ChromeSeleniumExternalBrowser : ExternalBrowserBase
{
    public ChromeSeleniumExternalBrowser() : base(ExternalBrowserType.ChromeSelenium)
    {
    }
}

public class ChromiumPuppeteerExternalBrowser : ExternalBrowserBase
{
    public ChromiumPuppeteerExternalBrowser() : base(ExternalBrowserType.ChromiumPuppeteer)
    {
    }
}

public class FirefoxSeleniumExternalBrowser : ExternalBrowserBase
{
    public FirefoxSeleniumExternalBrowser() : base(ExternalBrowserType.FirefoxSelenium)
    {
    }
}

そして、それを次のように使用します(申し訳ありませんが、私の変数名はひどいです):

ChromeSeleniumExternalBrowser:

ChromeSeleniumExternalBrowser chromeSelenium = new ChromeSeleniumExternalBrowser();
ChromeSeleniumReceiver chromeSeleniumReceiver = new ChromeSeleniumReceiver();
MoveMouseCommand chromeSeleniumMoveMouseCommand = 
    new MoveMouseCommand(chromeSeleniumReceiver, new Point(100, 110));
chromeSelenium.QueueCommand(chromeSeleniumMoveMouseCommand);
DelayCommand chromeSeleniumDelayCommand = new DelayCommand(chromeSeleniumReceiver, 5);
chromeSelenium.QueueCommand(chromeSeleniumDelayCommand);
ClickButtonCommand chromeSeleniumClickButtonCommand = 
    new ClickButtonCommand(chromeSeleniumReceiver, new Point(100, 100));
chromeSelenium.QueueCommand(chromeSeleniumClickButtonCommand);
MoveMouseLikeHumanCommand chromeSeleniumLerpMoveMouseCommand = 
    new MoveMouseLikeHumanCommand(chromeSeleniumReceiver, 
    new Point(100, 110), new Point(115, 120));
chromeSelenium.QueueCommand(chromeSeleniumLerpMoveMouseCommand);
chromeSelenium.ExecuteCommands();

// ChromeSeleniumExternalBrowser output:
// ChromeSeleniumReceiver : MoveMouse; {X=100,Y=110}
// ChromeSeleniumReceiver : Delay; delaying for 5 second(s)
// ChromeSeleniumReceiver : ClickButton; {X=100,Y=100}
// ChromeSeleniumReceiver : MoveMouse; {X=100,Y=110}
// ChromeSeleniumReceiver : MoveMouse; {X=101,Y=111}
// ChromeSeleniumReceiver : MoveMouse; {X=102,Y=112}
// ChromeSeleniumReceiver : MoveMouse; {X=103,Y=113}
// ChromeSeleniumReceiver : MoveMouse; {X=104,Y=114}
// ChromeSeleniumReceiver : MoveMouse; {X=105,Y=115}
// ChromeSeleniumReceiver : MoveMouse; {X=106,Y=116}
// ChromeSeleniumReceiver : MoveMouse; {X=107,Y=117}
// ChromeSeleniumReceiver : MoveMouse; {X=108,Y=118}
// ChromeSeleniumReceiver : MoveMouse; {X=109,Y=119}
// ChromeSeleniumReceiver : MoveMouse; {X=110,Y=120}
// ChromeSeleniumReceiver : MoveMouse; {X=111,Y=120}
// ChromeSeleniumReceiver : MoveMouse; {X=112,Y=120}
// ChromeSeleniumReceiver : MoveMouse; {X=113,Y=120}
// ChromeSeleniumReceiver : MoveMouse; {X=114,Y=120}
// ChromeSeleniumReceiver : MoveMouse; {X=115,Y=120}

FirefoxSeleniumExternalBrowser:

FirefoxSeleniumExternalBrowser firefoxSeleniumPilot = new FirefoxSeleniumExternalBrowser();
FirefoxSeleniumReceiver firefoxSeleniumReceiver = new FirefoxSeleniumReceiver();
MoveMouseCommand firefoxSeleniumMoveMouseCommand = 
    new MoveMouseCommand(firefoxSeleniumReceiver, new Point(50, 55));
firefoxSeleniumPilot.QueueCommand(firefoxSeleniumMoveMouseCommand);
ClickButtonCommand firefoxSeleniumClickButtonCommand = 
    new ClickButtonCommand(firefoxSeleniumReceiver, new Point(50, 55));
firefoxSeleniumPilot.QueueCommand(firefoxSeleniumClickButtonCommand);
MoveMouseLikeHumanCommand firefoxSeleniumLerpMoveMouseCommand = 
    new MoveMouseLikeHumanCommand(firefoxSeleniumReceiver, 
    new Point(50, 65), new Point(59, 69));
firefoxSeleniumPilot.QueueCommand(firefoxSeleniumLerpMoveMouseCommand);
firefoxSeleniumPilot.ExecuteCommands();

// output: 
// FirefoxSeleniumReceiver : MoveMouse; {X=50,Y=55}
// FirefoxSeleniumReceiver : ClickButton; {X=50,Y=55}
// FirefoxSeleniumReceiver : MoveMouse; {X=50,Y=65}
// FirefoxSeleniumReceiver : MoveMouse; {X=51,Y=66}
// FirefoxSeleniumReceiver : MoveMouse; {X=52,Y=67}
// FirefoxSeleniumReceiver : MoveMouse; {X=53,Y=68}
// FirefoxSeleniumReceiver : MoveMouse; {X=54,Y=69}
// FirefoxSeleniumReceiver : MoveMouse; {X=55,Y=69}
// FirefoxSeleniumReceiver : MoveMouse; {X=56,Y=69}
// FirefoxSeleniumReceiver : MoveMouse; {X=57,Y=69}
// FirefoxSeleniumReceiver : MoveMouse; {X=58,Y=69}
// FirefoxSeleniumReceiver : MoveMouse; {X=59,Y=69}

ChromiumPuppeteerExternalBrowser:

ChromiumPuppeteerExternalBrowser chromiumPuppeteerPilot = 
    new ChromiumPuppeteerExternalBrowser();
ChromiumPuppeteerReceiver chromiumPuppeteerReceiver = new ChromiumPuppeteerReceiver();
MoveMouseCommand chromiumPuppeteerMoveMouseCommand = 
    new MoveMouseCommand(chromiumPuppeteerReceiver, new Point(30, 40));
chromiumPuppeteerPilot.QueueCommand(chromiumPuppeteerMoveMouseCommand);
ClickButtonCommand chromiumPuppeteerClickButtonCommand = 
    new ClickButtonCommand(chromiumPuppeteerReceiver, new Point(30, 40));
chromiumPuppeteerPilot.QueueCommand(chromiumPuppeteerClickButtonCommand);
MoveMouseLikeHumanCommand chromiumPuppeteerLerpMoveMouseCommand = 
    new MoveMouseLikeHumanCommand(chromiumPuppeteerReceiver, 
    new Point(30, 40), new Point(39, 49));
chromiumPuppeteerPilot.QueueCommand(chromiumPuppeteerLerpMoveMouseCommand);
chromiumPuppeteerPilot.ExecuteCommands();

// output
// ChromiumPuppeteerReceiver : MoveMouse; {X=30,Y=40}
// ChromiumPuppeteerReceiver : ClickButton; {X=30,Y=40}
// ChromiumPuppeteerReceiver : MoveMouse; {X=30,Y=40}
// ChromiumPuppeteerReceiver : MoveMouse; {X=31,Y=41}
// ChromiumPuppeteerReceiver : MoveMouse; {X=32,Y=42}
// ChromiumPuppeteerReceiver : MoveMouse; {X=33,Y=43}
// ChromiumPuppeteerReceiver : MoveMouse; {X=34,Y=44}
// ChromiumPuppeteerReceiver : MoveMouse; {X=35,Y=45}
// ChromiumPuppeteerReceiver : MoveMouse; {X=36,Y=46}
// ChromiumPuppeteerReceiver : MoveMouse; {X=37,Y=47}
// ChromiumPuppeteerReceiver : MoveMouse; {X=38,Y=48}
// ChromiumPuppeteerReceiver : MoveMouse; {X=39,Y=49}
1
quaabaam