【问题标题】:Observer pattern: delay action after event trigger观察者模式:事件触发后延迟动作
【发布时间】:2018-11-02 16:55:32
【问题描述】:

我的代码工作正常。我只是从设计角度提出一个问题。

您可以复制/粘贴课程和测试课程 - 它们应该开箱即用。

问题描述:

我有一个类,它监视新/修改/删除文件的目录并在发生任何更改时触发事件。这是使用观察者模式实现的。

interface IFileObserver
{
    void onFileChange(String filename, String action);
}

public class FileWatcher implements Runnable
{

private Path _directory;
private WatchService _watchService;
private List<IFileObserver> _fileObserverList;

public FileWatcher(Path directory)
{
    _directory = directory;
    try
    {
        _watchService = FileSystems.getDefault().newWatchService();
        _directory.register(_watchService, ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE);
        _fileObserverList = new ArrayList<>();
    }
    catch (IOException e)
    {
        System.err.println("Unable to create FileWatcher object!");
    }
}

public void register(IFileObserver fileObserver)
{
    _fileObserverList.add(fileObserver);
}

private void updatesEvent(String filename, String action)
{
    for (IFileObserver observer : _fileObserverList)
    {
        observer.onFileChange(filename, action);
    }
}

private void startWatching()
{
    WatchKey key = null;
    boolean reset = true;
    while(reset)
    {
        try
        {
            key = _watchService.take();
            outerloop:
            for (WatchEvent<?> event : key.pollEvents())
            {
                String filename = event.context().toString();
                WatchEvent.Kind<?> kind = event.kind();
                switch(kind.name())
                {
                    case "ENTRY_CREATE":
                        updatesEvent(filename, kind.name());
                        // Files on create will normally trigger ENTRY_CREATE and ENTRY_MODIFY
                        // break to outerloop will prevent catching ENTRY_MODIFY when create is expected
                        break outerloop;
                    case "ENTRY_MODIFY":
                    case "ENTRY_DELETE":
                        updatesEvent(filename, kind.name());
                        break;
                }
            }
        }
        catch (InterruptedException e)
        {
            System.out.println("Retrieving watch key interrupted!");
        }

        reset = key.reset();
    }
}

@Override
public void run()
{
    startWatching();
}

问题是,要使用事件发送的数据我必须在 Thread.sleep 之前。正如您在下面的测试中看到的那样,我在每个断言块之前使用 Thread.sleep(100) - 没有测试失败。

public class FileWatcherTest implements IFileObserver
{
private int _updatesCount = 0;
private String _filename;
private String _action;

@Test
public void FileWatcher() throws IOException, InterruptedException
{
    File directory = new File(System.getProperty("user.dir"));
    assertTrue(directory.exists());
    assertSame(0, _updatesCount);

    Path path = Paths.get(directory.getPath());
    FileWatcher fileWatcher = new FileWatcher(path);
    fileWatcher.register(this);

    Thread thread = new Thread(fileWatcher);
    thread.start();

    String filename = "the-file-name.txt";
    File file = new File(filename);
    if (file.exists())
    {
        file.delete();
    }

    assertTrue(createFile(file));
    Thread.sleep(100);

    assertSame(1, _updatesCount);
    assertEquals(filename, _filename);
    assertEquals("ENTRY_CREATE", _action);

    assertTrue(modifyFile(file));
    Thread.sleep(100);

    assertSame(2, _updatesCount);
    assertEquals(filename, _filename);
    assertEquals("ENTRY_MODIFY", _action);

    Thread.sleep(250);
    assertTrue(deleteFile(file));
    Thread.sleep(100);

    assertSame(3, _updatesCount);
    assertEquals(filename, _filename);
    assertEquals("ENTRY_DELETE", _action);
}

private boolean deleteFile(File file)
{
    return file.delete();
}
private boolean modifyFile(File file) throws IOException
{
    FileWriter writer = new FileWriter(file, true);
    writer.append("another line appended by ENTRY_MODIFY");
    writer.close();
    return true;
}
private boolean createFile(File file) throws FileNotFoundException, UnsupportedEncodingException
{
    PrintWriter writer = new PrintWriter(file, "UTF-8");
    writer.println("The first line");
    writer.println("The second line");
    writer.close();
    return file.exists();
}

@Override
public void onFileChange(String filename, String action)
{
    _filename = filename;
    _action = action;
    _updatesCount++;
}

但我觉得这不太好,因为如果其他人使用这个类,他们不会知道你需要睡觉,这会导致问题。

是否可以以任何方式强制执行 Thread.sleep(100),即在 IFileObserver 接口的每个实现上?

【问题讨论】:

    标签: java observer-pattern watchservice


    【解决方案1】:

    你说的对。当您在系统中在 100 毫秒内收到创建通知时,在我的系统中大约需要 8400。因此,更好的测试设计是等待逻辑状态改变一段时间,而不是仅仅等待Thread.sleep。像

    private void waitUntilUpdateCountChangesFrom(int count) throws InterruptedException {
        int sleepTime = 10;
        int maxPolls = 10000/sleepTime;
        int i=0;
        for (; i<maxPolls; i++) {
            if (_updatesCount != count) {
                break;
            }
            Thread.sleep(sleepTime); // sleeps a little to give a turn to file watcher thread
        };
        System.out.println("waited " + (i*sleepTime) + " milliseconds");
    }
    

    然后像这样称呼它

        assertTrue(modifyFile(file));
    
        waitUntilUpdateCountChangesFrom(1);
    
        assertSame(2, _updatesCount);
        assertEquals(filename, _filename);
        assertEquals("ENTRY_MODIFY", _action);
    

    我注意到的另一件事是,有时在我的系统中,我也会首先收到 ENTRY_MODIFY 来创建文件。因此,期望 MODIFY 或 CREATE 来创建文件可能是一个好主意,如下所示:-

        assertTrue(createFile(file));
    
        waitUntilUpdateCountChangesFrom(0);
    
        assertSame(1, _updatesCount);
        assertEquals(filename, _filename);
        List<String> createActions = Arrays.asList("ENTRY_CREATE", "ENTRY_MODIFY");
        assertTrue(createActions.contains(_action));
    

    我的处理流程几乎如下所示:-

        Going to create .... 
        Received event: ENTRY_CREATE on file: the-file-name.txt
        waited 8440 milliseconds
        Going to modify .... 
        Received event: ENTRY_MODIFY on file: the-file-name.txt
        waited 8430 milliseconds
        Going to delete .... 
        Received event: ENTRY_DELETE on file: the-file-name.txt
        waited 8420 milliseconds
    

    【讨论】:

    • ENTRY_MODIFY 用于文件创建是一种预期行为。这是因为当你创建一个文件时,在 hud 下它不仅被创建,还有更多的过程步骤。因此,即使您添加 ENTRY_CREATE,您仍然会收到有关修改的通知。为了解决这个问题,我创建了一个观察到的文件列表,并比较了列表中的内容和不在列表中的内容。
    猜你喜欢
    • 1970-01-01
    • 2019-11-17
    • 1970-01-01
    • 2016-11-01
    • 2021-06-26
    • 1970-01-01
    • 1970-01-01
    • 2017-06-20
    • 1970-01-01
    相关资源
    最近更新 更多