デザインパターンの 7 つの設計原則#
1. 単一責任原則#
1.1 コードの表示と UML クラス図#
反面ケース
package singlePrinciple.negative;
public class StudentManager {
public void getConnection(String url) {
System.out.println("と"+url+"接続を確立");
}
public void searchStudent(String condition) {
System.out.println("に基づいて"+condition+"学生情報を検索");
}
public void deleteStudent(String condition) {
System.out.println("に基づいて"+condition+"学生情報を削除");
}
public void display() {
System.out.println("学生情報を表示");
}
/**
* 1.接続するデータベースの方法を変更するには、getConnectionを変更する必要があります
*
*/
}
正面ケースの UML クラス図
正面ケースの DBUtil.java
package singlePrinciple.positive;
// ツールクラス
public class DBUtil {
public void getConnection(String url) {
System.out.println("と"+url+"接続を確立");
}
}
Demo.java
package singlePrinciple.positive;
public class Demo {
public static void main(String[] args) {
DBUtil db=new DBUtil();
StudentDao dao=new StudentDao(db);
StudentManager sm=new StudentManager(dao);
db.getConnection("MySQL");
dao.searchStudent("孫悟空");
sm.display();
}
/**
* 1.DBUtilはデータベース接続操作をカプセル化しており、データベースサーバーを変更するには、このクラスまたは設定ファイルを変更するだけで済みます
* 2.StudentDaoはデータベースのCRUD操作をカプセル化しており、CRUD操作が比較的簡単に実現できます
*
*/
}
次に StudentDao.java
package singlePrinciple.positive;
public class StudentDao {
private DBUtil db;
public StudentDao(DBUtil db) {
super();
this.db = db;
}
public void searchStudent(String condition) {
System.out.println("に基づいて"+condition+"学生情報を検索");
}
public void deleteStudent(String condition) {
System.out.println("に基づいて"+condition+"学生情報を削除");
}
}
最後の StudentManager.java
package singlePrinciple.positive;
public class StudentManager {
private StudentDao dao;
public StudentManager(StudentDao dao) {
super();
this.dao = dao;
}
public void display() {
System.out.println("学生情報を表示");
}
}
1.2 単一責任原則の定義と作用(略称 SRP)#
定義:クラスは変化の原因を 1 つだけ持つべきであり、オブジェクトは 1 つの責任のみを含むべきです。これは最もシンプルで理解しやすいが、最も実現が難しい設計原則であり、クラスの粒度を制御するために使用されます。
作用:
==1. クラスの複雑さが低下 ==
==2. 可読性が向上 ==
==3. メンテナンス性が向上 ==
==4. システムの拡張性とメンテナンス性に大きな助けとなる ==
==5. 単一責任原則は高い内聚性と低い結合性を実現するための指針 ==
2. 開閉原則(オープン・クローズド原則)#
2.1 コードの表示#
現在はなし
2.2 定義と作用(略称 OCP)#
-
「拡張に対してオープン」:エンティティの振る舞いは拡張可能であることを示します。アプリケーションの要求が変わった場合、モジュールを拡張して新しい振る舞いを満たすことができます。
-
「修正に対してクローズ」:エンティティを拡張する際に、モジュールのソースコードやバイナリコードを変更する必要がないことを示します。
作用:
==(1)単体テストを行いやすくする;==
==(2)再利用性を高める;==
==(3)メンテナンス性を向上させる;==
==(4)オブジェクト指向のオープンな要求に適合する;==
3. インターフェース分離原則(略称 ISP)#
3.1 コード#
反面例、インターフェース分離を採用していない例。
package InterfaceIsolation.negative;
public interface HandleScore {
void update();
void insert();
void delete();
void print();
void search();
}
package InterfaceIsolation.negative;
public class Student implements HandleScore {
@Override
public void update() {
// TODO 自動生成されたメソッドスタブ
}
@Override
public void insert() {
// TODO 自動生成されたメソッドスタブ
}
@Override
public void delete() {
// TODO 自動生成されたメソッドスタブ
}
@Override
public void print() {
// TODO 自動生成されたメソッドスタブ
System.out.println("学生の成績を出力!!!!!");
}
@Override
public void search() {
// TODO 自動生成されたメソッドスタブ
}
/*
* インターフェースの汚染が存在
* 学生は成績を検索することしかできず、変更や記録ができない
* インターフェースを実装すると、成績を出力する以外のインターフェースは使えない
*/
}
インターフェース分離原則を採用した後のケースの UML は以下の通りです:
package InterfaceIsolation.positive;
public interface Modifyable {
void delete();
void insert();
}
package InterfaceIsolation.positive;
public interface Printable {
void print();
}
package InterfaceIsolation.positive;
public interface Searchable {
void search();
}
package InterfaceIsolation.positive;
public interface Updateable {
void update();
}
package InterfaceIsolation.positive;
public class Student implements Searchable{
@Override
public void search() {
// TODO 自動生成されたメソッドスタブ
}
}
package InterfaceIsolation.positive;
public class Teacher implements Searchable,Updateable,Printable{
@Override
public void print() {
// TODO 自動生成されたメソッドスタブ
}
@Override
public void update() {
// TODO 自動生成されたメソッドスタブ
}
@Override
public void search() {
// TODO 自動生成されたメソッドスタブ
}
}
package InterfaceIsolation.positive;
public class AcademicSecretary implements Modifyable,Printable,Searchable{
@Override
public void search() {
// TODO 自動生成されたメソッドスタブ
}
@Override
public void print() {
// TODO 自動生成されたメソッドスタブ
}
@Override
public void delete() {
// TODO 自動生成されたメソッドスタブ
}
@Override
public void insert() {
// TODO 自動生成されたメソッドスタブ
}
}
3.2 定義と作用#
3.2.1 インターフェース分離原則:#
(1)クライアントは必要のないインターフェースに依存すべきではない;
(2)クラス間の依存関係は最新のインターフェースに基づくべきである;
インターフェース分離原則はインターフェースの使用を制約する原則です。
3.2.2 インターフェース#
(1)クラスインターフェース:interface で定義されたインターフェース;
(2)インスタンスインターフェース:Class で定義されたクラスもインターフェースの一種です;
3.2.3 分離#
==(1)クライアントと必要のないインターフェースを分離する、つまりクライアントは必要のないインターフェースを使用せず、そのインターフェースを使用するクライアントは関連するメソッドのみを知っている;==
==(2)各インターフェースは相対的に独立した役割を担うべきであり、やるべきことをやり、やるべきでないことをやらない、インターフェースには余分なメソッドがない。==
3.2.4 作用#
(1)インターフェースの汚染を避ける;
(2)柔軟性を高める;
(3)特定のサービスを提供する;
(4)高い内聚性を実現する。
4. 依存逆転原則(略称 DIP)#
4.1 コード#
4.1.1 この原則を使用しないケースは以下の通りです#
package Dependency.negative;
public class AutoRead {
public void read(TextRead r) {
r.read();
}
public void read(DocRead r) {
r.read();
}
public void read(ExlRead r) {
r.read();
}
public void read(XMLRead r) {
r.read();
/**
* ソースコードを変更することは多くのリスクを伴い、開閉原則に違反します
*/
}
}
package Dependency.negative;
public class Demo {
public static void main(String[] args) {
AutoRead ar=new AutoRead();
ar.read(new TextRead());
ar.read(new DocRead());
ar.read(new ExlRead());
}
/**
* この時点でXMLファイルを読み取る機能を追加する必要がある場合、新しいXML読み取り機能クラスを作成し、AutoReadメソッドにread機能を追加する必要があり、これにより開閉原則に違反します
*
*/
}
package Dependency.negative;
public class DocRead {
public void read() {
System.out.println("docファイルを読み取る!!!");
}
}
package Dependency.negative;
public class ExlRead {
public void read() {
System.out.println("Excelファイルを読み取る!!");
}
}
package Dependency.negative;
public class TextRead {
public void read() {
System.out.println("textファイルを読み取る!");
}
}
package Dependency.negative;
public class XMLRead {
public void read() {
System.out.println("xmlファイルを読み取る");
}
}
4.1.2 原則を使用したケースは以下の通りです:#
package Dependency.positive;
public interface Readable {
void read();
}
package Dependency.positive;
public class AutoRead{
public void read(Readable r) {
r.read();
}
}
package Dependency.positive;
public class Demo {
public static void main(String[] args) {
AutoRead ar=new AutoRead();
ar.read(new DocRead());
ar.read(new ExlRead());
ar.read(new TxtRead());
ar.read(new XMLRead());
}
}
package Dependency.positive;
public class DocRead implements Readable{
@Override
public void read() {
// TODO 自動生成されたメソッドスタブ
System.out.println("docファイルを読み取る");
}
}
package Dependency.positive;
public class ExlRead implements Readable{
@Override
public void read() {
// TODO 自動生成されたメソッドスタブ
System.out.println("xlsファイルを読み取る。");
}
}
package Dependency.positive;
public class TxtRead implements Readable{
@Override
public void read() {
// TODO 自動生成されたメソッドスタブ
System.out.println("txtファイルを読み取る。");
}
}
package Dependency.positive;
public class XMLRead implements Readable{
@Override
public void read() {
// TODO 自動生成されたメソッドスタブ
System.out.println("xmlファイルを読み取る");
}
}
4.2 定義と作用#
4.2.1 定義:#
高レベルモジュールは低レベルモジュールに依存すべきではなく、両者は抽象に依存すべきである。抽象は詳細に依存すべきではなく、詳細は抽象に依存すべきである。
本質的には、抽象(インターフェースまたは抽象クラス)を通じて、各クラスまたはモジュールの実装を互いに独立させ、相互に影響を与えず、モジュール間の疎結合を実現することです。
4.2.2 作用:#
==(1)クラス間の結合性を減少させる;==
==(2)システムの安定性を高め、並行開放によるリスクを低減する;==
==(3)コードの可読性とメンテナンス性を向上させる;==
5. リスコフの置換原則(略称 LSP)#
5.1 コード#
5.1.1 反例コード:#
package substitution.negative;
public class ReadDoc extends ReadFile{
@Override
public void read(String fileName) {
// TODO 自動生成されたメソッドスタブ
System.out.println("docファイルを読み取る:"+fileName);
}
}
package substitution.negative;
public class ReadFile {
public void read(String fileName) {
System.out.println("excelファイルを読み取る"+fileName);
}
}
package substitution.negative;
public class Demo {
public static void readFile(ReadFile rf,String fileName) {
rf.read(fileName);
}
public static void main(String[] args) {
readFile(new ReadFile(),"a.xsl");
readFile(new ReadDoc(),"b.xsl");
}
}
5.1.2 正面ケース:#
UML は以下の通りです:
package substitution.positive;
public interface Readable {
void read(String fileName);
}
package substitution.positive;
public class ReadDoc implements Readable{
@Override
public void read(String fileName) {
// TODO 自動生成されたメソッドスタブ
System.out.println("docファイルを読み取る:"+fileName);
}
}
package substitution.positive;
public class ReadXsl implements Readable{
@Override
public void read(String fileName) {
// TODO 自動生成されたメソッドスタブ
System.out.println("Excelファイルを読み取る:"+fileName);
}
}
package substitution.positive;
/**
*
* @author ASUS
* 継承には利点も欠点もある
* 継承は侵入的であり、結合を増加させる
*/
public class Demo {
public static void readFile(Readable rf,String fileName) {
rf.read(fileName);
}
public static void main(String[] args) {
Readable r=new ReadDoc();
readFile(r,"a.doc");
ReadXsl rs=new ReadXsl();
readFile(rs,"b.xsl");
}
}
5.2 定義と作用#
5.2.1 定義#
すべての基底クラスを参照する場所は、そのサブクラスオブジェクトを透明に使用できなければならず、サブクラスに置き換えた後、システムの動作は変わらない。
リスコフの置換原則は、継承関係が遵守すべき 4 つのルールを定義します:
==(1) サブクラスは親クラスの抽象メソッドを実装できますが、親クラスの非抽象メソッドをオーバーライドしてはいけません;==
==(2) サブクラスには独自の特有のメソッドを追加でき、サブクラスの個性を反映できます;==
==(3) サブクラスのメソッドが親クラスのメソッドを実装する場合(オーバーライド / オーバーロードまたは抽象メソッドを実装する場合)、メソッドの前提条件(すなわちメソッドの入力パラメータ)は親クラスのメソッドの入力パラメータよりも緩やかであるか、等しい必要があります;==
==(4) サブクラスのメソッドが親クラスのメソッドを実装する場合(オーバーライド / オーバーロードまたは抽象メソッドを実装する場合)、メソッドの後提条件(すなわちメソッドの出力 / 戻り値)は親クラスよりも厳格であるか、等しい必要があります。==
5.2.2 作用#
(1)継承の乱用を制約し、開閉原則の一形態を実現する;
(2)プログラムの堅牢性を強化し、プログラムの変更に対して非常に良い互換性を持たせ、プログラムのメンテナンス性と拡張性を向上させる;
(3)要求の変更によって引き起こされるリスクを低減する。
6. 組み合わせ再利用原則(略称 CRP)#
6.1 コード#
反面:
package combined.negative;
class Person{
}
class Teacher extends Person{
}
class Student extends Person{
}
class PrimarySchoolTeacher extends Teacher{
}
class JuniorSchoolTeacher extends Teacher{
}
class HighSchoolTeacher extends Teacher{
}
class PrimaryStudent extends Student{
public void say() {
System.out.println("私は小学生です");
}
}
class JuniorStudent extends Student{
public void say() {
System.out.println("私は中学生です");
}
}
class HighStudent extends Student{
public void say() {
System.out.println("私は高校生です");
}
}
class Logistics extends Person{
}
class PrimaryLigistics extends Logistics{
}
class JuniorLigistics extends Logistics{
}
class HighLigistics extends Logistics{
}
public class Demo {
public static void main(String[] args) {
}
}
正面:
package combined.positive;
abstract class Person{
private String name;
private SchoolLevel level;
public Person(String name, SchoolLevel level) {
super();
this.name = name;
this.level = level;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLevel() {
return this.level.getLevel();
}
public void setLevel(SchoolLevel level) {
this.level = level;
}
}
class Teacher extends Person{
public Teacher(String name, SchoolLevel level) {
super(name, level);
// TODO 自動生成されたコンストラクタスタブ
}
public void say() {
System.out.println(super.getName()+"は"+super.getLevel()+"の先生です");
}
}
class Student extends Person{
public Student(String name, SchoolLevel level) {
super(name, level);
// TODO 自動生成されたコンストラクタスタブ
}
public void say() {
System.out.println(super.getName()+"は"+super.getLevel()+"の学生です");
}
}
interface SchoolLevel{
String getLevel();
}
class PrimaryLevel implements SchoolLevel{
@Override
public String getLevel() {
// TODO 自動生成されたメソッドスタブ
return "小学校";
}
}
class JuniorLevel implements SchoolLevel{
@Override
public String getLevel() {
// TODO 自動生成されたメソッドスタブ
return "中学校";
}
}
class HighLevel implements SchoolLevel{
@Override
public String getLevel() {
// TODO 自動生成されたメソッドスタブ
return "高校";
}
}
class Logistics extends Person{
public Logistics(String name, SchoolLevel level) {
super(name, level);
// TODO 自動生成されたコンストラクタスタブ
}
public void say() {
System.out.println(super.getName()+"は"+super.getLevel()+"の後勤管理者です");
}
}
public class Demo {
public static void main(String[] args) {
SchoolLevel level=new HighLevel();
Student swk=new Student("孫悟空",level);
swk.say();
Teacher puti=new Teacher("扶梯老祖",new PrimaryLevel());
puti.say();
Logistics cattle=new Logistics("牛魔王",new JuniorLevel());
cattle.say();
}
}
6.2 定義と作用#
6.2.1 定義#
オブジェクトの組み合わせ(集約)を優先して使用し、再利用の目的を達成するために継承を使用しない。
新しいオブジェクトの中で、既存のオブジェクトを関連関係(組み合わせや集約関係を含む)を通じて使用し、それを新しいオブジェクトの一部とし、新しいオブジェクトは既存のオブジェクトのメソッドを委任して呼び出すことで再利用機能を達成します。
つまり、再利用の目的を達成するために、できるだけ組み合わせ(contains-a)や集約(has-a)を使用し、継承(is-a)を使用しないようにします。
6.2.2 作用#
(1)実装が簡単です。継承を通じて、サブクラスは親クラスの機能を持ちます;
(2)拡張が容易です。サブクラスは新しい属性やメソッドを追加して親クラスを拡張します。
組み合わせ再利用原則の作用
(1) 新しいオブジェクトがメンバーオブジェクトにアクセスする唯一の方法は、メンバーオブジェクトのインターフェースを通じてであり、この再利用はブラックボックス再利用であり、システムをより柔軟にし、クラス間の結合度を低下させます;
(2) メンバーオブジェクトの内部の詳細は新しいオブジェクトには見えず、この再利用はカプセル化をサポートし、依存関係を減少させ、あるクラスの変更が他のクラスに与える影響は相対的に少なくなります;
(3) 各新しいクラスは 1 つのタスクに集中できます;
(4) この再利用は実行時に動的に行うことができ、新しいオブジェクトはメンバーオブジェクトと同じタイプのオブジェクトを動的に参照できます。
不足している点:== この方法で構築されたシステムは管理する必要のあるオブジェクトが多くなります。==
7. ディミテルの法則(LOD)#
ディミテルの法則を使用しない例
package lkp.negative;
public class ExcelToDB {
private String fileName;
private String db;
public ExcelToDB(String fileName, String db) {
super();
this.fileName = fileName;
this.db = db;
}
public String read() {
return this.fileName+"ファイルの内容!";
}
public void connectionDB() {
System.out.println("接続"+this.db+"データベースシステム");
}
public void save(String conten) {
System.out.println("を"+this.read()+"データベースに保存!!");
}
}
クライアントコード(クライアントが公開するコードが多すぎる)
package lkp.negative;
//ディミテルの法則の反例
public class Client {
public static void main(String[] args) {
ExcelToDB todb=new ExcelToDB("a.xsl","MySQL");
String str=todb.read();
todb.connectionDB();
todb.save(str);
}
}
ディミテルの法則の利点、== クライアントは変換方法を知る必要がなく、ツールクラスの変換メソッドを呼び出すだけで情報変換プロセスを完了でき、クライアントは最小限の知識でタスクを完了しました。==
ディミテルの法則:オブジェクトは他のオブジェクトについてできるだけ少なく理解すべきであり、見知らぬ人と話すべきではない、最小知識原則(==LKP==)。
オブジェクト間の相互作用をできるだけ減少させ、あるオブジェクトが別のオブジェクトのメソッドを呼び出す必要がある場合は、第三者を介して呼び出すことができます。
友人のメンバー:
==1. 現在のオブジェクト自身(this)==
==2. 現在のオブジェクトメソッドのパラメータ ==
==3. 現在のオブジェクトのインスタンス変数間で参照されるオブジェクト ==
==4. 現在のオブジェクトのインスタンス変数が集約である場合、集約の要素も友人 ==
==5. 現在のオブジェクトメソッドが作成したオブジェクト ==
4 つの要求:
== 優先的にクラスを不変クラスに設定することを考慮する ==
== できるだけクラスのアクセス権を低下させる ==
==Serializable の使用に注意する ==
== できるだけメンバーのアクセス権を低下させる ==
package lkp.positive;
public class ExcelToDB {
private String fileName;
private String db;
public ExcelToDB(String fileName, String db) {
super();
this.fileName = fileName;
this.db = db;
}
// プライベートメソッド、公開を避ける
private String read() {
return this.fileName+"ファイルの内容!";
}
private void connectionDB() {
System.out.println("接続"+this.db+"データベースシステム");
}
private void save(String conten) {
System.out.println("を"+this.read()+"データベースに保存!!");
}
public void perform() {
String str=this.read();
this.connectionDB();
this.save(str);
}
}
仲介者は以下の通りです
package lkp.positive;
public class UtilConvert {
private ExcelToDB todb;
public UtilConvert(ExcelToDB todb) {
super();
this.todb = todb;
}
public void convert() {
todb.perform();
}
}
クライアント
package lkp.positive;
public class Client {
public static void main(String[] args) {
ExcelToDB todb=new ExcelToDB("a.xsl","MYSQL");
UtilConvert convert=new UtilConvert(todb);
convert.convert();
}
}