Веб-браузер в качели с MVC


Последующий вопрос: Использование ООП при создании веб-браузер

После того, как я получила помощь по вопросу выше, я создал 3 класса для меня Browser. Browser мой "вид"-класс, который обрабатывает графический интерфейс модели интуитивно понятна и History содержит списки и их функциональность.

Прежде всего, это решение хорошо с MVC от точки зрения? А также, Мой History класс использует статические счетчики, потому что это было лучшее решение я смог придумать, учитывая, что я хочу, чтобы они были одинаковыми независимо от того, где они используются, но есть лучший способ использовать счетчики без их статической? Мне сказали, что через static следует избегать, в то время как в нуб этапах программирования.

Browser:

import java.awt.BorderLayout;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;

public class Browser{

    public JTextField addressBar;
    public JEditorPane display;
    private JButton button;
    private JPanel panel;
    private String URL = new String();
    private JButton backward;
    private JButton forward;
    private JFrame frame;
    private History history;

    public Browser(Model controller, History history) {
        this.history = history;
        frame = new JFrame("My Browser");
        panel = new JPanel();
        addressBar = new JTextField("Enter a URL");
        addressBar.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                URL = e.getActionCommand().toString();
                controller.newURL(URL);
            }
        });
        display = new JEditorPane();
        display.setEditable(false);
        display.addHyperlinkListener(new HyperlinkListener() {
            public void hyperlinkUpdate(HyperlinkEvent e) {
                if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
                    URL = e.getURL().toString();
                    controller.newURL(URL);
                }
            }
        });
        addButton("Close").addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (e.getSource() != null) {
                    controller.closeAction();
                }
            }
        });

        forward = addButton("Forward");
        forward.setEnabled(false);
        forward.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (e.getSource() != null) {
                    controller.forwardAction();
                }
            }
        });

        backward = addButton("Back");
        backward.setEnabled(false);
        backward.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (e.getSource() != null) {
                    controller.backwardAction();
                }
            }
        });

        addButton("History").addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (e.getSource() != null) {
                    controller.historyAction();
                }
            }
        });
        setframe();
    }

    private void setframe() {
        frame.add(addressBar, BorderLayout.NORTH);
        frame.add(panel, BorderLayout.SOUTH);
        frame.add(new JScrollPane(display), BorderLayout.CENTER);
        frame.setSize(400, 400);
        frame.setVisible(true);
    }

    private JButton addButton(String name) {
        button = new JButton(name);
        panel.add(button);
        return button;
    }

    public void initBackward() {
        if (History.i > 0) {
            backward.setEnabled(true);
        } else {
            backward.setEnabled(false);
        }
    }

    public void initForward() {
        if (History.i < history.list.size() - 1) {
            forward.setEnabled(true);
        } else {
            forward.setEnabled(false);
        }
    }
}

Model:

import javax.swing.JEditorPane;
import javax.swing.JOptionPane;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;

public class Model{
    private Browser browser;
    private History history;

    public Model() {
        history = new History();
        browser = new Browser(this, history);
    }

    public void loadURL(String URL) {
        try {
            browser.display.setPage(URL);
            browser.addressBar.setText(URL);
        } catch (Exception e) {
            history.removefromList(history.list);
            history.removefromList(history.previousPages);
            JOptionPane.showMessageDialog(null, "fel länk");
        }
    }

    public void closeAction() {
        System.exit(0);
    }

    public void forwardAction() {
        History.i++;
        browser.initBackward();
        browser.initForward();
        loadURL(history.list.get(History.i));
    }

    public void backwardAction() {
        History.i--;
        browser.initForward();
        browser.initBackward();
        loadURL(history.list.get(History.i));
    }

    public void historyAction() {
        String html = new String();
        for (String link : history.previousPages) {
            html = html + "<a href=\"" + link + "\">" + link + "</a>\n";
        }
        html = "<html><body" + html + "</body></html>";
        JEditorPane ep = new JEditorPane("text/html", html);
        ep.addHyperlinkListener(new HyperlinkListener() {
            public void hyperlinkUpdate(HyperlinkEvent e) {
                if (e.getEventType().equals(HyperlinkEvent.EventType.ACTIVATED)) {
                    loadURL(e.getURL().toString());
                }
            }
        });
        ep.setEditable(false);
        JOptionPane.showMessageDialog(null, ep);
    }


    public void newURL(String URL) {
        History.i++;
        History.j++;
        history.checklist();
        loadURL(URL);
        history.addtoList(history.previousPages, URL);
        history.addtoList(history.list, URL);
        browser.initBackward();
        browser.initForward();
    }

}

History:

import java.util.ArrayList;
import java.util.List;

public class History {
    public static int i = -1;
    public static int j = -1;
    public List<String> list = new ArrayList<>();
    public List<String> previousPages = new ArrayList<>();

    public void checklist() {
        if (i != j) {
            list.subList(i, list.size()).clear();
            j = i;
        }
    }

    public void addtoList(List<String> list, String URL) {
        list.add(URL);
    }

    public void removefromList(List<String> list) {
        list.remove(list.size()-1);
    }
}

MyFrame:

public class MyFrame {

    public static void main(String[] args) {
        new Model();
    }
}


255
3
задан 6 февраля 2018 в 12:02 Источник Поделиться
Комментарии
2 ответа

Сначала я должна извиниться, назвав его MVC в мой ответ на ваш исходный вопрос. После прочтения Жерве.ответ б я понял, что то, что я называю "архитектура MVC" не является обычным способом ее реализации. После некоторых поисков я узнал, что то, что я пытался объяснить более известный как МВА (вид модели адаптера).

Главное отличие заключается в том, что я делаю "просмотр" как в GUI (в вашем случае класс браузера). У меня история как "модель" (которая в МВА не связанным непосредственно на вид). И, наконец, у меня есть некоторые привязки класса, который обеспечивает связь между видом и моделью (которую я "ошибочно" называют контроллер и от которых вы, вероятно, запутался). Так давайте исправим это, обеспечивая дополнительные советы, чтобы улучшить ваш дизайн.

Первый шаг: переименование класса модели к чему-то другому. Моя первая мысль будет что-то вроде BrowserHistoryBinding но это название открыто для предложений.

Чтобы быть еще более очевидной, давайте тоже переименуем Browser для BrowserGUI, который снова открыт для предложений (даже держа его в браузере может быть хорошо, всегда можно отрефакторить класс имена позже, если вы можете придумать лучшее название).

Исправить теперь это:


Мне сказали, что использование статических следует избегать, в то время как в стадии нуб в программировании.

Хотя статические поля их использует (настоятельно не рекомендуется, хотя) i и j переменные в истории класса не должны быть статичными. Просто удалить static модификатор. (Обратите внимание, это ломает все ссылки, но мы исправим позже). Хорошим примером статического поля, когда они являются константами, они также final и разные соглашения об именовании. (См. BLANK_PAGE константа в примере ниже).

Так как мы очищаем History класс мы могли бы также взглянуть на то, что государство должно и не должно быть.

Переменная j представляющее количество элементов в истории. Нам не нужна эта переменная с list.size() дает нам эту информацию уже. Так j могут быть удалены полностью.

Переменная previousPages не используется. Это также могут быть полностью удалены. С другой стороны, название previousPages на самом деле вписывается в намерение переменной list намного лучше. Так давайте переименуем list для previousPages.

И, наконец, переменной i можно также использовать более осмысленные имена. От того, что я могу сказать, он представляет собой указатель на текущую страницу, так назовем ее currentPageIndex вместо.

Теперь, когда мы сократили класса, очень важно состояние, давайте также посмотрим на то, что мы должны обеспечить, чтобы get информация об этом состоянии и add новые страницы в истории.

public class History {
public static final String BLANK_PAGE = "about:blank";
private List<String> previousPages = new ArrayList<String>();

private int currentPageIndex = 0;

/**
* Removes any existing pages after the current active page and than adds the newUrl to the history.
*/
public void addUrl(String newUrl) {
removeNextPages();
previousPages.add(newUrl);
currentPageIndex++;
}

private void removeNextPages() {
previousPages.subList(currentPageIndex, previousPages.size()).clear();
}

public boolean hasPreviousPage() {
return currentPageIndex > 0;
}

public boolean hasNextPage() {
return currentPageIndex < previousPages.size() - 1;
}

/**
* Goes back 1 page in the history and returns that page.
* If no previous page exists return "about:blank" instead to default to a blank page.
*/
public String goBack() {
if (!hasPreviousPage()) {
return BLANK_PAGE;
}

currentPageIndex--;
return previousPages.get(currentPageIndex);
}

/**
* Goes forward 1 page in the history and returns that page.
* If no next page exists return "about:blank" instead to default to a blank page.
*/
public String goForward() {
if (!hasNextPage()) {
return BLANK_PAGE;
}

currentPageIndex++;
return previousPages.get(currentPageIndex);
}
}

Некоторые вещи замечать здесь.

Никакой другой класс не имеет доступа к государственной истории только через публичные методы. Другие классы даже не знают, что история хранится в списке и что он отслеживает текущую страницу с помощью int. Если мы хотим изменить реализацию, чтобы что-то еще нужно только этот класс менять.

removeNextPages это частная так как никакой другой класс должен вызвать этот метод. Это называется изнутри addUrl метод, потому что это единственный раз, когда мы хотим удалить все страницы из нашего списка.

goBack и goForward проверьте, если на входе действует. Я решил просто сохранить внутреннее состояние последовательного и возвращает пустую страницу. Альтернативным решением могло быть, чтобы вместо того, чтобы бросать исключение. Я добавил требуемый метод комментарии, чтобы сказать человек, использующий эти методы, что он может рассчитывать.


Теперь, когда мы очистили History класс Давайте взглянем на Browser класс.

Это уже выглядит довольно хорошо. За исключением 2-х "основных" вопросов и некоторые незначительные, что доставит некоторое неудобство.

Проблема 1: есть общественные поля. Прям как на уроке истории, мы не хотим любой другой класс, чтобы узнать о том, как мы храним состояние этого класса. Любое публичное поле, значит, мы не можем поменять ее, не затрагивая другие классы. Давайте просто сделаем эти частные и исправить остальные BrowserHistoryBinding класс, когда мы закончим с этим.

Проблема 2: BrowserGUI не должно содержать ссылок на History класс. Просто удалите параметр истории из конструктора (он все равно не используется). И заменить initBackward и initForward методы следующие 2:

public void setBackwardEnabled(boolean enabled) {
backward.setEnabled(enabled);
}

public void setForwardEnabled(boolean enabled) {
forward.setEnabled(true);
}

Обратите внимание, что проверку логики, могут ли эти 2 кнопки должны быть включены, это ответственность не только самому классу браузеров. Кто-то другой будет проверять логикой, а потом сообщить браузеру, чтобы включить или отключить эти кнопки.

Для несовершеннолетнего, что доставит некоторое неудобство:
Обратите внимание, как я переименовал initX методы setXEnabled. Это был, наверное, и моя вина, что не понятно по поводу именования на мой другой ответ. initX предназначена для вспомогательных методов, используемых в ходе строительства (или отложенной инициализации) класса. Здесь мы set-Инг новых значений при нормальной эксплуатации класса. Так set-Эрс стандартные названия для этих методов.

По этой же причине ваш setFrame метод должен быть переименован в initFrame поскольку этот метод используется для инициализации фрейма на строительство.

Вы не должны иметь поля button. Это должно быть определено локально внутри метода, а не:

private JButton addButton(String name) {
JButton button = new JButton(name);
panel.add(button);
return button;
}

Вы также не нуждаетесь в поле URL. Просто определить его как строку в 2 местах он используется:

    addressBar.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String url = e.getActionCommand();
controller.newURL(url);
}
});

display.addHyperlinkListener(new HyperlinkListener() {
@Override
public void hyperlinkUpdate(HyperlinkEvent e) {
if (Objects.equals(e.getEventType(), HyperlinkEvent.EventType.ACTIVATED)) {
String url = e.getURL().toString();
controller.newURL(url);
}
}
});

Заметьте также, что я пусть мой язь изменить == для Objects.equals( ... ) вместо. Равенство строки не должны быть проверены с ==.

Но хватит придирки. Эти мелкие детали должны быть указаны, чтобы вы с помощью нормальной IDE. Например, я использую IntelliJ. Еще один популярный-это затмение, как свободно использовать, но есть и другие, так что ваш выбор какой из них использовать.


Наконец, давайте посмотрим на как исправить привязку класса. Поскольку мы сделали состояние других 2 классов полностью частным, мы нарушили большинство этого класса.

Давайте начнем сверху с loadURL метод. Этот метод используется исключительно внутри этого класса, поэтому мы можем сделать его частным. Другие классы, наверное, не стоит в любом случае назвать это непосредственно. Мы также сталкиваемся с первой проблемой. А именно, что мы не обеспечивают способ сделать BrowserGUI на самом деле показывают определенный URL-адрес. Итак, давайте начнем, добавляя, что метод класса BrowserGUI`:

public void showURL(String url) throws IOException {
display.setPage(url);
addressBar.setText(url);
}

Обратите внимание, как display.setPage(url) может вызвать исключение, но мы не заинтересованы в решении, что исключение здесь. Вместо этого мы просто пусть весь метод кинуть на абонента и их обрабатывать. С помощью этого метода добавил, Мы можем продолжить в привязке класса.

public void loadURL(String URL) {
try {
browser.display.setPage(URL);
browser.addressBar.setText(URL);
} catch (Exception e) {
//history.removefromList(history.list);
//history.removefromList(history.previousPages);
JOptionPane.showMessageDialog(null, "fel länk");
}
}

Для простоты я просто закомментировал history.removefromList линии. Вы могли бы дать метод на уроках истории очистить историю и называю history.clearHistory() для достижения такого же результата как у тебя сейчас.

На следующий разбитые способ:

    public void forwardAction() {
String newUrl = history.goForward();
browser.setForwardEnabled(history.hasNextPage());
browser.setBackwardEnabled(history.hasPreviousPage());
loadURL(newUrl);
}

Обратите внимание, как я сейчас говорю с history для goForward вместо доступа к государственной и обновления, которые непосредственно. В History сам класс будет обрабатывать все, чтобы сохранить это государство последовательно и вернется на следующий URL-адрес в истории. Наша привязка класса не нужно знать, как он делает это.
То же самое для включения кнопки Вперед-Назад в браузере. Мы сейчас просто говорю браузера для включения это кнопки на основе полученной информации. В то же время мы извлечь эту информацию из истории, потому что привязка класса не должен знать, что значит "предыдущая страница" или "следующая страница". Это History класс должен знать, так как информация о нашей модели.

Аналогичные рассуждения для следующего метода:

public void backwardAction() {
String newUrl = history.goBack();
browser.setForwardEnabled(history.hasNextPage());
browser.setBackwardEnabled(history.hasPreviousPage());
loadURL(newUrl);
}

И теперь, когда я думаю об этом, каждый раз, когда мы изменить URL-адрес мы также хотим обновить вперед и назад кнопки. Так почему бы не поставить тех, кто внутри loadURL метод вместо этого?

public void loadURL(String url) {
try {
browser.showURL(url);
} catch (Exception e) {
JOptionPane.showMessageDialog(null, "fel länk");
}

browser.setForwardEnabled(history.hasNextPage());
browser.setBackwardEnabled(history.hasPreviousPage());
}

public void forwardAction() {
loadURL(history.goForward());
}

public void backwardAction() {
loadURL(history.goBack());
}

И пока мы здесь давайте все же исправить newURL метод с той же самой аргументацией:

public void newURL(String url) {
history.addUrl(url);
loadURL(url);
}

Разве это не выглядит намного проще, чем раньше?

Наконец-то есть historyAction метод, когда мы попадаем в неприятности. Здесь мы хотим использовать список страниц в History но в то же время я сказал вам, что мы не хотим сделать государство любого класса населения. Так давайте решать это путем предоставления геттер в классе истории получить весь список страниц:

public List<String> getAllPages(){
return previousPages;
}

Это может выглядеть глупо, так как мы до сих пор просто дать доступ к нашим внутренним списком справа? Но есть хорошая причина для этого. Ранее я говорил, что всякий раз, когда мы хотим изменить реализацию следует только изменить конкретный класс? Это до сих пор держат? Ну, скажем, например, что вместо 1 список с полной историей мы бы разбить его на 2 списка. В previousPages что теперь только содержит страницы до текущей страницы. И еще один список nextPages который содержит все страницы после текущей страницы. Затем мы меняем реализации данного получателя:

public List<String> getAllPages(){
List<String> result = new ArrayList<String>(previousPages);
result.addAll(nextPages);
return result;
}

Обратите внимание, как сигнатура этого метода не изменилась. Смысл любого класса, который уже использовал его раньше, можно просто использовать обновленную версию, не зная, что внутреннее представление изменилось.

Теперь, чтобы закончить то, что начали здесь по фиксированной реализации historyAction метод.

public void historyAction() {
String html = "";
for (String link : history.getAllePages()) {
html = html + "<a href=\"" + link + "\">" + link + "</a>\n";
}
html = "<html><body" + html + "</body></html>";
JEditorPane ep = new JEditorPane("text/html", html);
ep.addHyperlinkListener(new HyperlinkListener() {
@Override
public void hyperlinkUpdate(HyperlinkEvent e) {
if (e.getEventType().equals(HyperlinkEvent.EventType.ACTIVATED)) {
loadURL(e.getURL().toString());
}
}
});
ep.setEditable(false);
JOptionPane.showMessageDialog(null, ep);
}

И все должно снова работать. Заметим, однако, что было бы лучше, чтобы посмотреть ручка показывать пользователю этот список предыдущих страницах. Но вот что-то для следующего обновления.


Я надеюсь, что этот довольно длинный пост вам лучше разобраться в том, как я бы реализовать это приложение браузера. Как Жерве.Б показали, что это не единственный возможный путь. Есть преимущества и dissadvantages как всегда. Поэтому стоит изучать также и другие способы, и попытаться выяснить, чья реализация лучше подойдет для вашей программы. По крайней мере, общее мнение состоит в том, что это хорошая идея, чтобы попытаться отделить интерфейс от кода бизнес-логики.

1
ответ дан 6 февраля 2018 в 09:02 Источник Поделиться


это решение хорошо с MVC от точки зрения?

Нет это не так. Ваш вид может быть Browser но ваш Model есть модель и контроллер (вы называете это controller в browser кстати). И History это модель.

В модели должны быть использованы для содержит ваш браузер государство (url, содержание, история).

class BrowserModel extends Observable {

void goTo(URL newUrl) {
history.add(newUrl); // Hide your i++, j++ and other logic
content = read(newUrl);
onChange();
}
}

На вид (Browser) надо уметь слушать модели и обновлять, когда это изменится.

void onUrlChanged(URL newUrl) {
addressBar.setText(newUrl);
}

void onContentChanged(byte[] content) {
display.setContent(new String(content, ..));
}

Недостающие контроллер должен быть ответственным реагировать на события пользователя. Он может слушать события от Browser компоненты или использовать более универсальный механизм (в случае, если вы хотите изменить ваш взгляд технологии). Он реагирует на эти события при изменении состояния представления и/или обновление модели.

view.addressBar.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
URL target = e.getActionCommand().toString();
view.setLoading();
model.goTo(target);
}
});

Эти простые изменения позволят вам двигаться к лучшему модели MVC, где контроллер реагирует на ввод данных пользователем, чтобы изменить модель и где посмотреть сам обновлялся при изменении модели.

Когда вы там, вы можете начать, чтобы инкапсулировать свои компоненты и использовать осмысленные имена. Специально для History где логика, чтобы добавить новый URL-адрес должен быть скрыт внутри класса сама.

1
ответ дан 6 февраля 2018 в 01:02 Источник Поделиться