プライマー
シャオシュアイさんは 25 歳、ラオ・ワンさんは 35 歳ですが、ある日、シャオシュアイさんとラオ・ワンさんは、若者と中年の人の違いがますます顕著になってきているという問題について話し合いました。
シャオシュアイ氏はいくつかのシナリオを挙げ、同時にシャオシュアイ氏は若者の代表として、老王氏は中年層の代表としてそれぞれ回答した。
1. 職場ですぐに解決できない問題に遭遇した場合、たとえば、リーダーがあなたに対して偏見を持っている場合、または仕事のタスクが重すぎる場合、どうすればよいですか?
シャオシュアイ:私はここにはいません、私には自分の居場所があるので、すぐに転職します。
ラオ・ワン: まずは我慢して、それから耐えてください。幸運な日が来るかもしれません。結局のところ、毎月支払わなければならない住宅ローンがまだたくさんあるのですから。
2. 年末ボーナスはどのように手配すればよいですか?
シャオシュアイ:最新の携帯電話やその他の電化製品を買いに行って、楽しんで、今日は酔ってください。
ラオ・ワン: 住宅ローンを返済するために全部使ってください。
3. 週末はどのように過ごしますか?
シャオシュアイ:睡眠とゲームの2つです。
ラオ・ワン:家事と子守りの2つです。
若者と中年が一緒に遊べないのも無理はない、これがジェネレーションギャップだ。
Lao Wang 氏は言いました。「たくさん話しましたが、上記の会話をコードで書いていただけますか?」
Xiaoshuai: 本当に尊敬します。すべてがコードに関係する可能性があります。わかりました、今日は機嫌が良いので、あなたのために書きます。
人の抽象クラス:
/**
* 人的抽象类
*/
public abstract class Person {
/**
* 场景
*/
protected String scene;
public Person(String scene) {
this.scene = scene;
}
/**
* 回答
*/
public abstract void answer();
}
若い人間:
/**
* 年轻人
*/
public class Young extends Person{
public Young(String scene) {
super(scene);
}
@Override
public void answer() {
if("工作不顺利".equals(scene)) {
System.out.println("年轻人:此处不留爷,自有留爷处,立马换工作。");
} else if("年终奖".equals(scene)) {
System.out.println("年轻人:去买个最新款的手机或者其他电子产品,好好爽一爽,今朝有酒今朝醉。");
} else if("周末".equals(scene)) {
System.out.println("年轻人:两件事,睡觉,打游戏。");
}
}
}
中年の人間:
/**
* 中年人
*/
public class MiddleAged extends Person{
public MiddleAged(String scene) {
super(scene);
}
@Override
public void answer() {
if("工作不顺利".equals(scene)) {
System.out.println("中年人:先忍忍,再熬熬,说不定有时来运转的一天,毕竟每个月还有这么多房贷要还呢。");
} else if("年终奖".equals(scene)) {
System.out.println("中年人:全部拿去还房贷。");
} else if("周末".equals(scene)) {
System.out.println("中年人:两件事,干家务,带娃。");
}
}
}
クライアントクラス:
/**
* 客户端
*/
public class Client {
public static void main(String[] args) {
List<Person> personList = new ArrayList<>();
personList.add(new Young("工作不顺利"));
personList.add(new MiddleAged("工作不顺利"));
personList.add(new Young("年终奖"));
personList.add(new MiddleAged("年终奖"));
personList.add(new Young("周末"));
personList.add(new MiddleAged("周末"));
personList.stream().forEach(f -> f.answer());
}
}
出力:
年轻人:此处不留爷,自有留爷处,立马换工作。
中年人:先忍忍,再熬熬,说不定有时来运转的一天,毕竟每个月还有这么多房贷要还呢。
年轻人:去买个最新款的手机或者其他电子产品,好好爽一爽,今朝有酒今朝醉。
中年人:全部拿去还房贷。
年轻人:两件事,睡觉,打游戏。
中年人:两件事,干家务,带娃。
それを読んだ後、Lao Wang氏は次のように述べた。「文章は良いですが、まだいくつか問題があります。問題のシナリオをいくつか追加したい場合は、たとえば、普段どのような本を読んでいますか、どのスポーツが好きですか、何時ですか?」夜は寝ますか?など。
Xiaoshuai: それは簡単ではありません。Young クラスと MiddleAged クラスに if...else 条件を追加するだけです。
老王は首を振って言った、「これは開閉の原則に違反しています。修正には閉鎖され、拡張には開放されています。」これらがサードパーティによって提供されたクラスである場合、変更することはできませんが、新しい動作を追加したい場合はどうすればよいでしょうか?
「既存のコードを変更せずに、既存のクラスに新しい動作を追加しますか? どうやってそんなことが可能なのでしょうか?」Xiaoshuai 氏は疑問を抱きました。
ラオ・ワンは微笑んでこう言いました。「それは不可能です。これには設計パターンがあります。」
訪問者のパターン
ビジター モード: 特定のオブジェクト構造内の各要素に作用する操作を提供し、要素クラスを変更せずに要素に作用する新しい操作を定義します。
ビジター パターンは、既存のコードを変更せずに、既存のクラス階層に新しい動作を追加できる動作設計パターンです。
- ビジター (Scene などのビジター)
抽象クラスまたはインターフェイス。ビジターがアクセスできる要素を宣言します。具体的には、プログラム内で、getYoungAnswer メソッドは young オブジェクトにアクセスでき、getMiddleAgedAnswer メソッドは middleAged オブジェクトにアクセスできます。 - ConcreteVisitor (WorkNotWell、 YearEndAwards、Weekend などの具体的な訪問者) は
、特定の操作を実装する訪問者インターフェイスの実装クラスです。 - Element (人物などの要素)
インターフェイスまたは抽象クラスは、訪問者をパラメーターとして受け取る受け入れ操作を定義します。 - ConcreteElement (Young や MiddleAged などの特定の要素) は、
通常、visitor.visit(this) モードで受け入れ操作を実装します。 - ObjectStructure (オブジェクト構造) は、
その要素を簡単に走査できるようにオブジェクトのコレクションを格納します。
シーンクラス:
/**
* 场景
*/
public interface Scene {
/**
* 年轻人的回答
* @param young
*/
public void getYoungAnswer(Young young);
/**
* 中年人的回答
* @param middleAged
*/
public void getMiddleAgedAnswer(MiddleAged middleAged);
}
悪い作業現場:
/**
* 工作不顺
*/
public class WorkNotWell implements Scene{
@Override
public void getYoungAnswer(Young young) {
System.out.println(young.name + ":此处不留爷,自有留爷处,立马换工作。");
}
@Override
public void getMiddleAgedAnswer(MiddleAged middleAged) {
System.out.println(middleAged.name + ":先忍忍,再熬熬,说不定有时来运转的一天,毕竟每个月还有这么多房贷要还呢。");
}
}
年末の表彰シーン:
/**
* 年终奖
*/
public class YearEndAwards implements Scene{
@Override
public void getYoungAnswer(Young young) {
System.out.println(young.name + ":去买个最新款的手机或者其他电子产品,好好爽一爽,今朝有酒今朝醉。");
}
@Override
public void getMiddleAgedAnswer(MiddleAged middleAged) {
System.out.println(middleAged.name + ":全部拿去还房贷。");
}
}
週末のシーン:
/**
* 周末
*/
public class Weekend implements Scene{
@Override
public void getYoungAnswer(Young young) {
System.out.println(young.name + ":两件事,睡觉,打游戏。");
}
@Override
public void getMiddleAgedAnswer(MiddleAged middleAged) {
System.out.println(middleAged.name + ":两件事,干家务,带娃。");
}
}
ヒューマンインターフェース:
/**
* 人的接口
*/
public interface Person {
/**
* 接受
*/
public void accept(Scene scene);
}
若い人間:
/**
* 年轻人
*/
public class Young implements Person{
protected String name;
public Young(String name) {
this.name = name;
}
@Override
public void accept(Scene scene) {
scene.getYoungAnswer(this);
}
}
中年の人間:
/**
* 中年人
*/
public class MiddleAged implements Person{
protected String name;
public MiddleAged(String name) {
this.name = name;
}
@Override
public void accept(Scene scene) {
scene.getMiddleAgedAnswer(this);
}
}
オブジェクト構造クラス:
/**
* 对象结构
*/
public class ObjectStructure {
private List<Person> personList = new ArrayList<>();
/**
* 新增
* @param person
*/
public void add(Person person) {
personList.add(person);
}
/**
* 删除
* @param person
*/
public void delete(Person person) {
personList.remove(person);
}
/**
* 遍历显示
* @param scene
*/
public void display(Scene scene) {
personList.stream().forEach(f -> f.accept(scene));
}
}
クライアントクラス:
/**
* 客户端
*/
public class Client {
public static void main(String[] args) {
ObjectStructure objectStructure = new ObjectStructure();
objectStructure.add(new Young("小帅"));
objectStructure.add(new MiddleAged("老王"));
// 工作不顺利的场景
WorkNotWell workNotWell = new WorkNotWell();
objectStructure.display(workNotWell);
// 年终奖的场景
YearEndAwards yearEndAwards = new YearEndAwards();
objectStructure.display(yearEndAwards);
// 周末的场景
Weekend weekend = new Weekend();
objectStructure.display(weekend);
}
}
出力:
小帅:此处不留爷,自有留爷处,立马换工作。
老王:先忍忍,再熬熬,说不定有时来运转的一天,毕竟每个月还有这么多房贷要还呢。
小帅:去买个最新款的手机或者其他电子产品,好好爽一爽,今朝有酒今朝醉。
老王:全部拿去还房贷。
小帅:两件事,睡觉,打游戏。
老王:两件事,干家务,带娃。
Lao Wang 氏は Xiaoshuai 氏にこう言いました。「訪問者パターンを適用し、既存のコードを変更せずに既存のクラスに新しい動作を追加することで実現できます。」
たとえば、「夜の睡眠時間」シーンを追加したい場合、Sleep クラスを追加して Scene インターフェイスを実装するだけで済みます。
/**
* 睡觉
*/
public class Sleep implements Scene{
@Override
public void getYoungAnswer(Young young) {
System.out.println(young.name + ":十二点半才睡,精力旺盛。");
}
@Override
public void getMiddleAgedAnswer(MiddleAged middleAged) {
System.out.println(middleAged.name + ":十点半就睡,早睡早起身体好。");
}
}
次に、このシナリオを実現するために、関連する呼び出しコードを Client クラスに追加します。
// 睡觉的场景
Sleep sleep = new Sleep();
objectStructure.display(sleep);
小帅:十二点半才睡,精力旺盛。
老王:十点半就睡,早睡早起身体好。
Young クラスと MiddleAged クラスをまったく変更せずに新しい操作を追加できるのは、驚くべきことではないでしょうか。
Xiaoshuai は少し混乱しています: 非常に興味深いですが、このコードは少し奇妙に見えます。なぜ accept メソッドはシーン オブジェクトを渡し、次に独自のオブジェクトを渡すのですか?
これは他人のメソッドを自分で呼んでそこに身を置くのと同じではないでしょうか?
@Override
public void accept(Scene scene) {
scene.getYoungAnswer(this);
}
シングルディスパッチとダブルディスパッチ
ラオ・ワンは大声で笑って言った、「頑張ってください、シャオシュアイ、あなたの発言はとても面白いです、具体的に話しましょう。」
WorkNotWell、Weekend、および YearEndAwards の 3 つのシナリオは、若者と中年者にどのように関連していますか?
実際には、中間に 2 つのステップがあります。
まず、表示メソッドを呼び出すときに、特定のシーンを決定するために特定のシーン実装クラスが渡されます。
次に、accept メソッドに this オブジェクトが渡され、特定の要素オブジェクト (Young) も決定されるため、両方のオブジェクトが決定されます。
ここで、単一派遣(Single Dispatch)と二重派遣(Double Dispatch)の概念についてお話したいと老王氏は続けた。
いわゆる単一ディスパッチとは、オブジェクトが実行されるメソッドを指しますが、これはオブジェクトの実行時の型に従って決定され、オブジェクトのどのメソッドを実行するかは、メソッド パラメータのコンパイル時の型に従って決定されます。
いわゆるダブルディスパッチとは、オブジェクトの実行時タイプに応じてオブジェクトのどのメソッドを実行するか、メソッドパラメータの実行時タイプに応じて実行するオブジェクトのメソッドを決定することを指します。
まずコードの一部を見てみましょう。
public class Parent {
public void f() {
System.out.println("我是父类的方法f()");
}
}
public class Child extends Parent{
@Override
public void f() {
System.out.println("我是子类的方法f()");
}
}
/**
* 单分派
*/
public class SingleDispatch {
public void show(Parent p) {
p.f();
}
public void overloadFunction(Parent p) {
System.out.println("我是父类参数重载方法:overloadFunction(Parent p)");
}
public void overloadFunction(Child c) {
System.out.println("我是子类参数重载方法:overloadFunction(Child c)");
}
}
public class Test {
public static void main(String[] args){
SingleDispatch singleDispatch = new SingleDispatch();
Parent p = new Child();
singleDispatch.show(p);
singleDispatch.overloadFunction(p);
}
}
出力:
我是子类的方法f()
我是父类参数重载方法:overloadFunction(Parent p)
単一ディスパッチの図:
Java は単一ディスパッチ言語であるため、オブジェクトが実行されるメソッドは、オブジェクトのランタイム型によって決まります。pf() の p は Child オブジェクトを実行するため、Child クラスのメソッドが実行されます。
オブジェクトのどのメソッドを実行するかは、メソッド パラメータのコンパイル時の型によって異なります。コンパイル時の型は Parent です。
したがって、 public void overloadFunction(Parent p) メソッドが呼び出されます。
Java 言語は単一ディスパッチのみをサポートするため、二重ディスパッチを実現するにはビジター パターンを使用する必要があります。そのため、ConcreteElement 実装クラスが Element インターフェイスではなく ConcreteVisitor クラスで使用されます。
次に、このオブジェクトを accept メソッドに渡して、呼び出すメソッドを決定します。
@Override
public void accept(Scene scene) {
scene.getYoungAnswer(this);
}
要約する
オブジェクトが複雑な構造を持ち、要素が安定していて変更が難しい場合、つまり、ConcreteElement クラスが比較的安定していて不用意に追加されることはないが、この構造に対する新しい操作を頻繁に定義する必要がある場合、ビジターモードの使用に適しています。
アドバンテージ
- オープンとクローズの原則に従い、既存のコードを変更せずに既存のクラスに新しい動作を追加します。
- 関連する動作を 1 つの訪問者オブジェクトにグループ化することで、要素クラスを簡素化します。
欠点がある
- 新しい ConcreteElement クラスの追加は面倒で、新しい ConcreteElement クラスを追加するたびに、すべての Visitor クラスに新しい操作を追加する必要があります。
常に新しい ConcreteElement クラスが追加されると、Vistor クラスとそのサブクラスの保守が困難になるため、ビジター パターンが適しています。
ビジター パターンはアクセス操作を増やしやすくなりますが、要素の追加が難しくなるため、比較的安定した要素を持つ構造に適しています。