1

MVC アーキテクチャのバージョンを使用してプログラムを作成しました。このコードの目的は、Web ページのリストの h1 タイトルをスクレイピングし、結果を JTable に返すことです。

これまでのところ、プログラムは正常に動作しています。私が望むように結果を返していますが、最後までテーブルを更新しません。結果が出たらテーブルを更新したいのですが、学習しているだけなので、ベストプラクティスの原則を考慮した方法でこれを行いたいと思っています。

必要に応じてこれを更新すると思いますが、コードを少し変更する必要があります。GUI を動的に更新する最善の方法がわかりません (スレッド、オブザーバー、その他の何か?)。「このコードを MVC パターンのどこに置くべきか?」という質問があるかどうかさえわかりません。理にかなっていますか?

とにかくここに私の見解があります:

public class SearchView extends JFrame{
//Components
private JLabel selectElementLabel = new JLabel("Element Selector:");
private JTextField selectElement = new JTextField("h1");;
private JComboBox<String> selectLocale; 

private DefaultTableModel tableModel = new DefaultTableModel();
private JTable resultTable = new JTable(tableModel);

private JLabel statusLabel;
private JButton runButton = new JButton("Run");
private JButton clearButton = new JButton("Clear");

private SearchModel s_model;

//Constructor
public SearchView(SearchModel model) {
    //Set the Logic here(model)
    s_model = model;    

    //Initialise Components here(model)
    selectLocale = new JComboBox<>(s_model.getLocales());
    selectLocale.setSelectedIndex(13);

    //Layout Components
    JPanel userInputPanel = new JPanel();
    userInputPanel.setLayout(new BoxLayout(userInputPanel, BoxLayout.X_AXIS));
    userInputPanel.add(selectElementLabel);
    userInputPanel.add(selectElement);
    userInputPanel.add(selectLocale);

    tableModel.addColumn("Page");
    tableModel.addColumn("Data");
    resultTable.setFillsViewportHeight(true);

    JScrollPane resultScroller = new JScrollPane(resultTable);
    resultScroller.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
    resultScroller.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
    resultScroller.setAlignmentX(Component.LEFT_ALIGNMENT);

    JPanel controlButtons = new JPanel();
    controlButtons.setLayout(new FlowLayout(FlowLayout.RIGHT));
    controlButtons.add(statusLabel = new JLabel(s_model.getState()));
    controlButtons.add(clearButton);
    controlButtons.add(runButton);


    this.setTitle("Element Searcher");
    this.add(BorderLayout.NORTH, userInputPanel);
    this.add(BorderLayout.CENTER, resultScroller);
    this.add(BorderLayout.SOUTH, controlButtons);
    this.setExtendedState(Frame.MAXIMIZED_BOTH); 
    this.setMinimumSize(new Dimension(900, 600));
    this.setVisible(true);  
    this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);        
}

void reset(){
    tableModel.setRowCount(0);
}

String getSelectedElement(){
    return selectElement.getText();
}

String getSelectedLocale(){
    return selectLocale.getSelectedItem().toString();
}

void setResults(Object[] result){
    tableModel.addRow(result);
}

void addRunListener(ActionListener run){
    runButton.addActionListener(run);
}

void addClearListerner(ActionListener clear){
    clearButton.addActionListener(clear);
}

}

コントローラ:

public class SearchController {

private SearchModel s_model;
private SearchView s_view;

public SearchController(SearchModel model, SearchView view) {
    s_model = model;
    s_view = view;

    s_view.addRunListener(new RunListener());
    s_view.addClearListerner(new ClearListener());

}

class RunListener implements ActionListener{
    public void actionPerformed(ActionEvent e){
        String selectedLocale = null;
        try {
            selectedLocale = s_view.getSelectedLocale();
            s_model.setPageList(selectedLocale);
            for (String pageUrl : s_model.getPageList()){
                s_view.setResults(s_model.getResults(pageUrl));
            }
        } catch (Exception e1) {
            System.out.println(e1);
        }
    }
}

class ClearListener implements ActionListener{
    public void actionPerformed(ActionEvent e){
        s_model.reset();
        s_view.reset();
    }
}

}

そして最後に私のモデル:

public class SearchModel {
//Constants
private static final String[] localeStrings = { "cs-cz", "da-dk", "de-at", "de-ch", "de-de", "el-gr", "en-ae", "en-au", "en-ca", "en-gb", "en-ie", "en-in", "en-nz", "en-us", "en-za", "es-cl", "es-co", "es-es", "es-mx", "fi-fi", "fr-be", "fr-ca", "fr-ch", "fr-fr", "hu-hu", "it-it", "ja-jp", "ko-kr", "nb-no", "nl-be", "nl-nl", "pl-pl", "pt-br", "pt-pt", "ru-ru", "sk-sk", "sv-se", "zh-hk", "zh-sg", "zh-tw" };
private static final String INITIAL_STATE = "idle";
private HashSet<String> pageList;
private Object[] scrapeResult;
private String locale = "en-us";

//Search State
private String searchState;

public SearchModel() {
    reset();
}

public void setPageList(String loc){
    locale = loc;
    ScrapeXML scraper = new ScrapeXML(locale);
    pageList = scraper.getUrls();
}

public void setResults(String page){
    ScrapeElements scraper = new ScrapeElements(page, locale);
    scrapeResult = scraper.getResults();
}

public void reset(){
    searchState = INITIAL_STATE;
}

public String[] getLocales(){
    return localeStrings;
}

public String getState(){
    return searchState;
}

public HashSet<String> getPageList(){
    return pageList;
}

public Object[] getResults(String page){
    setResults(page);
    return scrapeResult;
}

}

コード自体に関するコメントや提案があれば、お知らせください。

ありがとう!

4

1 に答える 1

1

リスナーパターンとしても知られているダブルディスパッチなしでは、実際にMVCにすることはできません。モデルに、追加する必要があります

 public void addListener(ModelListener listener) {
 }

そして(「あなたの」モデルが変更されたときに古いモデルを聞くのをやめることができます)

 public void removeListener(ModelListener listener) {
 }

そのため、通常は「リスナー」インターフェースを介して渡されるモデルの更新を受け取るために、ほとんど未知のオブジェクトを追加することができます。

 public interface ModelListener {

   public void modelChanged(ModelChangeEvent event);

 }

ModelChangeEvent は通常、次のようなものです

 public class ModelChangeEvent {

   private Model source;

   public ModelChangeEvent(Model source, <possibly other fields here>) {
     this.source = source;
   }

   public Model getSource() {
     return source;
   }
 }

これにより、リスナーはそのように振る舞うことができます

 public ModelView implements ModelListener {

  ... stuff ...

   public void modelChanged(ModelChangeEvent event) {
     if (event.getSource() == model) {
       // this is my model!
       name = model.getName();
       age = model.getAge();
       ... and any other updates that this listener cares about ...
     }
   }

リスナーが変更ハンドラー内で行う方法にはある程度の柔軟性があります。最も重要な部分は、柔軟性がリスナーのスコープに含まれていることです。モデルは基本的に、リッスンしているものを無視し、リッスンしているに集中することができます。

  ... in the model class ...

  private void notifyListeners() {
    ModelChangeEvent event = new ModelChangeEvent(this);
    for (ModelListener listener : listeners) {
      listener.modelChanged(event);
    }
  }

繰り返しますが、リスナーに通知する方法には大きな柔軟性がありますが、重要な要素は、監視可能なモデルの「要素」が変更されたときに、すべてのリッスン オブジェクトが呼び出しを受け取る必要があるということです。通常、プライベート メソッドに配置される理由は、モデルの再利用がはるかに簡単になるためです。

  ... in the model class ...
  public void setName(String name) {
    this.name = name;
    notifyListeners();
  }

各 Listening クラスでは、表示内容に応じて名前を読み上げる場合と読み上げない場合があります。

  ... in a name and age sensitive listening class ...
   public void modelChanged(ModelChangeEvent event) {
     if (event.getSource() == model) {
       name = model.getName();
       age = model.getAge();
     }
   }


  ... in a name insensitive listening class ...
   public void modelChanged(ModelChangeEvent event) {
     if (event.getSource() == model) {
       // this is my model!
       age = model.getAge();
     }
   }

このようなものを配置したら、ビューはモデルをリッスンする必要があるため、コントローラーは、ビューの関心のあるプレゼンテーション項目の定期的な更新と組み合わせて、モデルの監視可能な「要素」のある種のポーリングを行う必要はありません。

はい、これは、ビューがモデル内のデータのコピーを持っていることが多いことを意味します。これは良いことです。プレゼンテーションを変更する必要があると判断した場合、変更するデータのコピーがあるためです。「名前表示」ビューでは、次のように一貫した大文字化を強制したい場合があります。

  ... in a name and age sensitive listening class ...
  ... note that _name_ is the name in the _view_ not the model! ...
   public void modelChanged(ModelChangeEvent event) {
     if (event.getSource() == model) {
       name = capitalize(model.getName());
       age = model.getAge();
     }
   }

モデルとビューが常に同期されるようになりました。これにより、コントローラーのコードが大幅に簡素化され、コマンドを処理するだけで済みます。コマンドごとに、次の 1 つ (または複数) を実行する可能性があります。

  1. モデル内のメソッドを呼び出します (ビュー自体が自動更新されます)。
  2. ビューのモデルを検索して切り替えます (「次の顧客」のボタンのように、ビューを破棄せず、ビューのモデルを設定するだけです)。setModel(Model)これには、View にメソッドを記述する必要があります。
  3. ビューを作成または破棄します (おそらくモデルを設定します)。
于 2013-03-22T16:09:17.933 に答える