Les six principes des modèles de conception : 1. Le principe de responsabilité unique, dont le cœur est de contrôler la granularité des classes, de découpler les objets et d'améliorer leur cohésion 2. Le principe d'ouverture et de fermeture, qui peut être réalisé grâce à " ; contraintes abstraites et changements d'encapsulation" À atteindre ; 3. Le principe de substitution de Liskov explique principalement certains principes concernant l'héritage ; 4. Le principe d'inversion de dépendance réduit le couplage entre les clients et les modules d'implémentation ; 5. Le principe d'isolation d'interface consiste à contraindre les interfaces et à réduire les classes. paires. Dépendances des interfaces ; 6. La loi de Demeter impose de limiter la largeur et la profondeur de la communication entre les entités logicielles.
L'environnement d'exploitation de ce tutoriel : système windows7, version java8, ordinateur DELL G3.
Concernant les modèles de conception, j'ai lu de nombreux livres de modèles de conception il y a longtemps, certains plusieurs fois, et j'ai toujours espéré pouvoir utiliser ces modèles de conception lors du codage. Cependant, dans le codage quotidien, le singleton est le plus utilisé, suivi des modèles d'observateur et de constructeur (builder), qui sont utilisés plus fréquemment, et les autres sont rarement utilisés.
La raison pour laquelle nous ne pouvons pas les utiliser est que nous ne pouvons toujours pas comprendre l'idée des modèles de conception et que nous ne pouvons pas relier ces modèles de conception aux problèmes rencontrés dans le codage, nous ne pouvons donc pas utiliser les modèles de conception.
En fait, des modèles de conception sont proposés pour résoudre un problème courant. Alors, lorsque vous réfléchissez au modèle de conception à adopter, vous devez d’abord vous demander quel est le problème actuel ? Choisissez le modèle de conception approprié en fonction du problème.
Après vous être familiarisé avec les modèles de conception, vous constaterez qu'il existe une relation inclusive entre certains modèles de conception, et qu'ils sont même très similaires, mais que différents modèles de conception résolvent différents problèmes.
Lorsque nous concevons un module, nous pouvons le considérer sous les angles suivants :
Quelle est la relation entre ce module et les autres modules ?
Quelles parties du module restent inchangées, quelles parties changent constamment, et comment ?
Quelle est la relation entre les classes ? Pourquoi devons-nous en dépendre ?
Voulez-vous ajouter une interface ? Quel problème l’interface existe-t-elle pour résoudre ?
Bien entendu, cet article ne vous apprend pas à utiliser les modèles de conception. Il explique plutôt les principes de conception des modèles de conception. Les modèles de conception suivent également certaines règles lors de leur conception.
Six principes des modèles de conception, plus précisément les suivants :
Principe de responsabilité unique (classes et méthodes, interfaces)
Principe d'ouverture et de fermeture (ouvert pour expansion, fermé pour modification)
Principe de remplacement de Richter (relation entre classes de base et sous-classes)
Principe d'inversion de dépendance (s'appuyer sur des interfaces abstraites au lieu d'objets concrets)
Principe d'isolation des interfaces (les interfaces sont subdivisées selon les fonctions)
Loi de Déméter (la relation étroite entre les classes)
Il y a un parenthèse à côté de chaque principe de conception, qui est utilisé pour expliquer ou décrire le champ d'application. Chaque principe est décrit en détail ci-dessous.
Le principe de responsabilité unique (SRP) est également appelé principe de fonction unique. La responsabilité fait ici référence à la raison du changement de classe. Le principe de responsabilité unique stipule qu'une classe ne doit avoir qu'une seule raison pour son changement, sinon la classe doit être divisée (il ne doit jamais y avoir plus d'une raison pour une classe). changer).
Ce principe stipule que les objets ne doivent pas assumer trop de responsabilités. Si un objet assume trop de responsabilités, il y a au moins les deux inconvénients suivants :
Les changements dans une responsabilité peuvent affaiblir ou inhiber cette classe. mettre en œuvre d'autres responsabilités Capacité ;
Lorsque le client a besoin d'une certaine responsabilité de l'objet, il doit inclure toutes les autres responsabilités inutiles, ce qui entraîne un code redondant ou un gaspillage de code.
Avantages du principe de responsabilité unique
Le cœur du principe de responsabilité unique est de contrôler la granularité des classes, de découpler les objets et d'améliorer leur cohésion. Si vous suivez le principe de responsabilité unique, vous bénéficierez des avantages suivants.
Réduisez la complexité du cours. Si une classe n’est responsable que d’une seule responsabilité, sa logique est nettement plus simple que si elle est responsable de plusieurs responsabilités.
Améliorer la lisibilité des cours. La complexité est réduite et la lisibilité est naturellement améliorée.
Améliorer la maintenabilité du système. Une lisibilité améliorée facilitera naturellement sa maintenance.
Réduction des risques causés par les changements. Le changement est inévitable, et si le principe de responsabilité unique est bien suivi, lorsqu'une fonction est modifiée, l'impact sur les autres fonctions peut être considérablement réduit.
Comment mettre en œuvre le principe de responsabilité unique
Le principe de responsabilité unique est le principe le plus simple mais le plus difficile à appliquer. Il oblige les concepteurs à découvrir les différentes responsabilités d'une classe, à les séparer, puis à les encapsuler. les répartir dans différentes classes ou modules. Les multiples responsabilités des cours de découverte exigent que les concepteurs possèdent de solides capacités d’analyse et de conception ainsi qu’une expérience pertinente en refactoring.
Exemple
public interface UserService { public void login(String username, String password); public void register(String email, String username, String password); public void logError(String msg); public void sendEmail(String email); }
Ce code a évidemment un gros problème. UserService est responsable non seulement de l'enregistrement et de la connexion des utilisateurs, mais également de l'enregistrement du journal et de l'envoi d'e-mails, et le comportement de ce dernier est sensiblement différent de celui du premier. <br/>
这段代码很显然存在很大的问题,UserService 既要负责用户的注册和登录,还要负责日志的记录和邮件的发送,并且后者的行为明显区别于前者。<br/>
假设我要修改发送邮件的逻辑就得修改这个类,这时候 qa 还得回归登录注册逻辑,这样明显不合理。
因此我们需要进行拆分,根据具体的职能可将其具体拆分如下:
UserService:只负责登录注册
public interface UserService { public void login(String username, String password); public void register(String email, String username, String password); }
LogService :只负责日志<br/>
public interface LogService { public void logError(String msg); }
EmailService: 只负责发送邮件
public interface EmailService { public void sendEmail(String email); }
这时候,咱们再去回顾前面提到的优点,就能深深体会了。
这里只是讲了接口,其实对类也一样,甚至方法也是一样的。
对于类来说,根据类名,确保里面提供的方法都是属于这个类的。
对于方法,不要把不相关的对象实例作为参数传进来。如果你发现某个方法依赖某个不相关的对象,那么这个方法的实现可能就存在问题。
比如 android 中图片下载后显示到 imageView 中,我提供如下的方法:
loadImage(String url, ImageView view) { // 下载图片,展示图片 }
对于 loadImage 这个方法,参数 url 是ok 的,但是参数 ImageView 却是不合理的。因为这里做了两个操作,下载图片,展示图片。应该将这个方法在进行拆分:
// 下载图片 loadImage(String url) { } // 显示图片 displayImage(String url, ImageView view) { // 调用 getBitmap (url) 获取图片 // 获取图片后将其设置到 view 中。 } // 根据 url 获取图片, getBitmap(String url) { }
这样整个逻辑就很清晰。后续需要修改下载逻辑,也不会影响到展示逻辑。当然其实还有个问题是,这两个方法要不要放在一个类里面?
开闭原则的实现方法:可以通过“抽象约束、封装变化”来实现开闭原则,即通过接口或者抽象类为软件实体定义一个相对稳定的抽象层,而将相同的可变因素封装在相同的具体实现类中。
因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。而软件中易变的细节可以从抽象派生来的实现类来进行扩展,当软件需要发生变化时,只需要根据需求重新派生一个实现类来扩展就可以了。
示例
// 矩形 public class Rectangle { public double getWidth() { return width; } public double getHeight() { return height; } }
需要计算矩形的面积
// 面积计算器 public class AreaCalculator { public double area(Rectangle shape){ return shape.getWidth() * shape.getHeight(); } }
假设这时候,又多了一个圆形类
// 圆形 public class Circular { public double getRadius(){ return radius; } }
同样也需要计算他的面积,这时候就会变成下面这样子:
public class AreaCalculator { public double area(Object shape){ if(shape instanceof Rectangle) { Rectangle rectangle = (Rectangle) shape; return rectangle.getWidth() * rectangle.getHeight(); } else if (shape instanceof Circular) { Circular circular = (Circular) shape; return circular.getRadius() * circular.getRadius() * Math.PI; } else { throw new RuntimeException("There is no such type."); } } }
这么更改完成,完全没有问题。但是在真实的生产环境中,情况更为复杂,更改涉及的部分较多,那样就可能导致牵一发动全身。并且,以前编写的经过测试的一些功能需要重新测试,甚至导致某些功能不可用。
改进版,把计算面积这个公有逻辑变成一个接口:
public interface Shape { public double getArea(); } public class Rectangle implements Shape{ public double getWidth() { return width; } public double getHeight() { return height; } public double getArea() { return getWidth() * getHeight(); } }
这样,当需求变更,需要计算圆形面积的时候,我们只需创建一个圆形的类,并实现 Shape 接口即可:<br/>
En supposant que je souhaite modifier la logique d'envoi des emails, je dois modifier cette classe A ce moment, qa doit revenir au. logique de connexion et d’enregistrement, ce qui est évidemment déraisonnable.
Nous devons donc le diviser selon les fonctions spécifiques, il peut être divisé comme suit : 🎜🎜🎜UserService🎜 : uniquement responsable de la connexion et de l'enregistrement🎜🎜public class Circular implements Shape { public double getRadius(){ return radius; } public double getArea() { return getRadius() * getRadius() * Math.PI; } }
Seul responsable de la journalisation<br/>
🎜🎜class Customer { public void shopping(ShaoguanShop shop) { //购物 System.out.println(shop.sell()); } }
class Customer { public void shopping(WuyuanShop shop) { //购物 System.out.println(shop.sell()); } }
En ce moment, examinons les avantages mentionnés ci-dessus, vous le comprendrez en profondeur.
🎜🎜Je n'ai parlé ici que d'interfaces. En fait, c'est pareil pour les classes, et même les méthodes sont les mêmes. 🎜🎜Pour les classes, en fonction du nom de la classe, assurez-vous que toutes les méthodes fournies appartiennent à cette classe. 🎜🎜Pour les méthodes, ne transmettez pas d'instances d'objet non pertinentes en tant que paramètres. Si vous constatez qu'une méthode dépend d'un objet sans rapport, il peut y avoir un problème avec l'implémentation de la méthode. 🎜🎜Par exemple, après avoir téléchargé des images dans Android et les avoir affichées dans imageView, je propose la méthode suivante : 🎜🎜class Customer { public void shopping(Shop shop) { //购物 System.out.println(shop.sell()); } } class Customer { public void shopping(Shop shop) { //购物 System.out.println(shop.sell()); } }
package principle; public class DIPtest { public static void main(String[] args) { Customer wang=new Customer(); System.out.println("顾客购买以下商品:"); wang.shopping(new ShaoguanShop()); wang.shopping(new WuyuanShop()); } } //商店 interface Shop { public String sell(); //卖 } //韶关网店 class ShaoguanShop implements Shop { public String sell() { return "韶关土特产:香菇、木耳……"; } } //婺源网店 class WuyuanShop implements Shop { public String sell() { return "婺源土特产:绿茶、酒糟鱼……"; } } //顾客 class Customer { public void shopping(Shop shop) { //购物 System.out.println(shop.sell()); } }
顾客购买以下商品: 韶关土特产:香菇、木耳…… 婺源土特产:绿茶、酒糟鱼……
Besoin de calculer l'aire d'un rectangle
🎜🎜public interface UserService { public void login(String username, String password); public void register(String email, String username, String password); public void logError(String msg); public void sendEmail(String email); }
package principle; public class LoDtest { public static void main(String[] args) { Agent agent=new Agent(); agent.setStar(new Star("林心如")); agent.setFans(new Fans("粉丝韩丞")); agent.setCompany(new Company("中国传媒有限公司")); agent.meeting(); agent.business(); } } //经纪人 class Agent { private Star myStar; private Fans myFans; private Company myCompany; public void setStar(Star myStar) { this.myStar=myStar; } public void setFans(Fans myFans) { this.myFans=myFans; } public void setCompany(Company myCompany) { this.myCompany=myCompany; } public void meeting() { System.out.println(myFans.getName()+"与明星"+myStar.getName()+"见面了。"); } public void business() { System.out.println(myCompany.getName()+"与明星"+myStar.getName()+"洽淡业务。"); } } //明星 class Star { private String name; Star(String name) { this.name=name; } public String getName() { return name; } } //粉丝 class Fans { private String name; Fans(String name) { this.name=name; } public String getName() { return name; } } //媒体公司 class Company { private String name; Company(String name) { this.name=name; } public String getName() { return name; } }
Il faut aussi calculer son aire A ce moment, cela deviendra comme ceci :
🎜🎜粉丝韩丞与明星林心如见面了。 中国传媒有限公司与明星林心如洽淡业务。
De cette façon, lorsque les exigences changent et que nous devons calculer la surface circulaire, nous il suffit de créer une classe circulaire et d'implémenter l'interface Shape :<br/>
🎜🎜rrreee🎜计算三角形面积、四边形面积... 的时候,我们只需让它们去实现 Shape 接口即可,无需修改源代码。
Le principe de substitution de Richter explique principalement certains principes concernant l'héritage, c'est-à-dire quand l'héritage doit être utilisé, quand l'héritage ne doit pas être utilisé, et les principes qui le sous-tendent. La substitution de Liskov est à l'origine la base de la réutilisation de l'héritage. Elle reflète la relation entre les classes de base et les sous-classes, complète le principe d'ouverture et de fermeture et régule les étapes spécifiques pour réaliser l'abstraction.
Le rôle du principe de substitution de Richter
Les principales fonctions du principe de substitution de Richter sont les suivantes.
Le principe de substitution de Richter est l'un des moyens importants de réaliser le principe d'ouverture et de fermeture.
Il surmonte les défauts de mauvaise réutilisabilité causés par le remplacement des classes parentales dans l'héritage.
C'est la garantie d'une action correcte. Autrement dit, l’extension de la classe n’introduira pas de nouvelles erreurs dans le système existant, réduisant ainsi le risque d’erreurs de code.
Renforcez la robustesse du programme, et en même temps obtenez une très bonne compatibilité lors du changement, améliorez la maintenabilité et l'évolutivité du programme et réduisez les risques introduits lorsque les exigences changent.
Comment implémenter le principe de substitution de Richter (héritage)
En termes simples, le principe de substitution de Richter signifie : les sous-classes peuvent étendre les fonctions de la classe parent, mais ne peuvent pas modifier les fonctions d'origine du parent classe. En d'autres termes : lorsqu'une sous-classe hérite d'une classe parent, essayez de ne pas remplacer les méthodes de la classe parent, sauf en ajoutant de nouvelles méthodes pour compléter de nouvelles fonctions.
Sur la base de la compréhension ci-dessus, la définition du principe de substitution de Liskov peut être résumée comme suit :
Une sous-classe peut implémenter la méthode abstraite de la classe parent, mais elle ne peut pas remplacer la méthode non abstraite de la classe parent. class
Dans la sous-classe Vous pouvez ajouter vos propres méthodes uniques
Lorsqu'une méthode d'une sous-classe remplace une méthode d'une classe parent, les conditions préalables de la méthode (c'est-à-dire les paramètres d'entrée de la méthode ) sont plus lâches que la méthode de la classe parent
Lorsqu'une méthode d'une sous-classe implémente une méthode d'une classe parent (écrase/surcharge ou implémente une méthode abstraite), les postconditions de la méthode (c'est-à-dire la sortie/ valeur de retour de la méthode) sont plus strictes ou égales à celles de la classe parent
Bien qu'il soit simple d'écrire de nouvelles fonctions en réécrivant la méthode de la classe parent, la réutilisabilité de l'ensemble du système d'héritage sera relativement faible, surtout quand. Le polymorphisme est fréquemment utilisé, la probabilité d'erreurs d'exécution du programme sera très élevée.
Si le programme viole le principe de substitution de Liskov, l'objet de la classe héritée aura une erreur d'exécution là où la classe de base apparaît.
La méthode de correction à l'heure actuelle est la suivante : annuler la relation d'héritage d'origine et repenser la relation entre eux.
Concernant l'exemple du principe de substitution de Liskov, le plus connu est "un carré n'est pas un rectangle". Bien sûr, il existe de nombreux exemples similaires dans la vie. Par exemple, les manchots, les autruches et les kiwis sont classés comme des oiseaux d'un point de vue biologique ; ne peuvent pas être définis comme des sous-classes de « oiseau ». De même, puisque le « poisson ballon » ne peut pas nager, il ne peut pas être défini comme une sous-catégorie de « poisson » ; le « canon jouet » ne peut pas faire exploser les ennemis, il ne peut donc pas être défini comme une sous-catégorie de « canon », etc.
La meilleure façon pour les carrés et les rectangles est d'ajouter une autre classe parent, et ils hériteront de cette classe parent en même temps.
Le principe d'inversion de dépendance est l'un des moyens importants de mettre en œuvre le principe d'ouverture-fermeture, qui réduit le couplage entre les clients et les modules d'implémentation.
Parce que dans la conception logicielle, les détails sont modifiables, tandis que la couche d'abstraction est relativement stable, donc une architecture construite sur l'abstraction est beaucoup plus stable qu'une architecture construite sur les détails. Le résumé ici fait référence à l'interface ou à la classe abstraite, et les détails font référence à la classe d'implémentation spécifique.
Le but de l'utilisation d'interfaces ou de classes abstraites est de formuler des spécifications et des contrats sans impliquer d'opérations spécifiques, et de laisser le soin de montrer les détails à leurs classes d'implémentation.
Le rôle du principe de dépendance et d'inversion
Les principales fonctions du principe d'inversion de dépendance sont les suivantes.
Le principe d'inversion de dépendances permet de réduire le couplage entre classes.
Le principe d'inversion de dépendance peut améliorer la stabilité du système.
Le principe d'inversion de dépendance peut réduire les risques causés par le développement parallèle.
Le principe d'inversion de dépendances peut améliorer la lisibilité et la maintenabilité du code.
依赖倒置原则的实现方法
依赖倒置原则的目的是通过要面向接口的编程来降低类间的耦合性,所以我们在实际编程中只要遵循以下4点,就能在项目中满足这个规则。
每个类尽量提供接口或抽象类,或者两者都具备。
变量的声明类型尽量是接口或者是抽象类。
任何类都不应该从具体类派生。
使用继承时尽量遵循里氏替换原则。
依赖倒置原则在“顾客购物程序”中的应用。
分析:本程序反映了 “顾客类”与“商店类”的关系。商店类中有 sell() 方法,顾客类通过该方法购物以下代码定义了顾客类通过韶关网店 ShaoguanShop 购物
class Customer { public void shopping(ShaoguanShop shop) { //购物 System.out.println(shop.sell()); } }
但是,这种设计存在缺点,如果该顾客想从另外一家商店(如婺源网店 WuyuanShop)购物,就要将该顾客的代码修改如下:
class Customer { public void shopping(WuyuanShop shop) { //购物 System.out.println(shop.sell()); } }
顾客每更换一家商店,都要修改一次代码,这明显违背了开闭原则。
存在以上缺点的原因是:顾客类设计时同具体的商店类绑定了,这违背了依赖倒置原则。
解决方法是:定义“婺源网店”和“韶关网店”的共同接口 Shop,顾客类面向该接口编程,其代码修改如下:
class Customer { public void shopping(Shop shop) { //购物 System.out.println(shop.sell()); } } class Customer { public void shopping(Shop shop) { //购物 System.out.println(shop.sell()); } }
这样,不管顾客类 Customer 访问什么商店,或者增加新的商店,都不需要修改原有代码了,其类如下图所示:
package principle; public class DIPtest { public static void main(String[] args) { Customer wang=new Customer(); System.out.println("顾客购买以下商品:"); wang.shopping(new ShaoguanShop()); wang.shopping(new WuyuanShop()); } } //商店 interface Shop { public String sell(); //卖 } //韶关网店 class ShaoguanShop implements Shop { public String sell() { return "韶关土特产:香菇、木耳……"; } } //婺源网店 class WuyuanShop implements Shop { public String sell() { return "婺源土特产:绿茶、酒糟鱼……"; } } //顾客 class Customer { public void shopping(Shop shop) { //购物 System.out.println(shop.sell()); } }
程序的运行结果如下:
顾客购买以下商品: 韶关土特产:香菇、木耳…… 婺源土特产:绿茶、酒糟鱼……
Le principe de ségrégation d'interface (ISP) oblige les programmeurs à faire de leur mieux pour diviser les interfaces gonflées en interfaces plus petites et plus spécifiques, de sorte que l'interface ne contienne que la méthode d'intérêt du sens client.
En 2002, Robert C. Martin a défini le « principe d'isolation de l'interface » comme suit : les clients ne devraient pas être obligés de dépendre de méthodes qu'ils n'utilisent pas (les clients ne devraient pas être obligés de dépendre de méthodes qu'ils n'utilisent pas). Il existe une autre définition de ce principe : La dépendance d'une classe à une autre doit dépendre de la plus petite interface possible.
La signification des deux définitions ci-dessus est la suivante : établir les interfaces dédiées dont ils ont besoin pour chaque classe, plutôt que d'essayer de construire une énorme interface pour toutes les classes qui en dépendent pour appeler.
Le principe d'isolation de l'interface et le principe de responsabilité unique visent tous deux à améliorer la cohésion des classes et à réduire le couplage entre elles, incarnant l'idée d'encapsulation, mais les deux sont différents :
Le principe de responsabilité unique se concentre sur les responsabilités, tandis que le principe d'isolation de l'interface se concentre sur l'isolation des dépendances de l'interface.
Le principe de responsabilité unique contraint principalement les classes, qui visent la mise en œuvre et les détails du programme ; le principe d'isolation des interfaces contraint principalement les interfaces, principalement pour la construction de l'abstraction et le cadre global du programme.
Avantages du principe d'isolation d'interface
Le principe d'isolation d'interface est de contraindre les interfaces et de réduire la dépendance des classes aux interfaces. Suivre le principe d'isolation d'interface présente les 5 avantages suivants.
Décomposez l'interface volumineuse en plusieurs interfaces à petite granularité, ce qui peut empêcher la propagation des changements externes et améliorer la flexibilité et la maintenabilité du système.
L'isolation de l'interface améliore la cohésion du système, réduit les interactions externes et réduit le couplage du système.
Si la granularité de l'interface est définie raisonnablement, la stabilité du système peut être assurée cependant, si la définition est trop petite, cela entraînera trop d'interfaces et compliquera la conception si la définition est trop petite ; grande, la flexibilité sera réduite. L’incapacité de fournir des services personnalisés entraîne des risques imprévus pour l’ensemble du projet.
L'utilisation de plusieurs interfaces spécialisées peut également refléter la hiérarchie des objets, car la définition de l'interface globale peut être obtenue grâce à l'héritage d'interface.
peut réduire la redondance du code dans l'ingénierie de projet. Une interface trop volumineuse contient généralement de nombreuses méthodes inutilisées. Lors de l'implémentation de cette interface, vous êtes obligé de concevoir du code redondant.
Comment mettre en œuvre le principe d'isolation d'interface
Lors de l'application spécifique du principe d'isolation d'interface, il doit être mesuré selon les règles suivantes.
L'interface doit être aussi petite que possible, mais dans certaines limites. Une interface ne sert qu’un seul sous-module ou logique métier.
Personnalisez les services pour les classes qui s'appuient sur des interfaces. Fournissez uniquement les méthodes dont l’appelant a besoin et bloquez celles qui ne sont pas nécessaires.
Comprenez l'environnement et refusez de le suivre aveuglément. Chaque projet ou produit comporte des facteurs environnementaux sélectionnés. Différents environnements ont des normes différentes pour le fractionnement des interfaces. Comprendre la logique métier en profondeur.
Améliorer la cohésion et réduire les interactions extérieures. Faites en sorte que l'interface utilise le moins de méthodes pour accomplir le plus de choses.
Pour l'isolation d'interface, vous pouvez toujours vous référer à l'exemple mentionné en responsabilité unique :
public interface UserService { public void login(String username, String password); public void register(String email, String username, String password); public void logError(String msg); public void sendEmail(String email); }
这时候,应该就能理解拆分的好处了。
迪米特法则(Law of Demeter,LoD)又叫作最少知识原则(Least Knowledge Principle,LKP),产生于 1987 年美国东北大学(Northeastern University)的一个名为迪米特(Demeter)的研究项目,由伊恩·荷兰(Ian Holland)提出,被 UML 创始者之一的布奇(Booch)普及,后来又因为在经典著作《程序员修炼之道》(The Pragmatic Programmer)提及而广为人知。
迪米特法则的定义是:只与你的直接朋友交谈,不跟“陌生人”说话(Talk only to your immediate friends and not to strangers)。其含义是:如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。
迪米特法则中的“朋友”是指:当前对象本身、当前对象的成员对象、当前对象所创建的对象、当前对象的方法参数等,这些对象同当前对象存在关联、聚合或组合关系,可以直接访问这些对象的方法。
迪米特法则的优点
迪米特法则要求限制软件实体之间通信的宽度和深度,正确使用迪米特法则将有以下两个优点。
降低了类之间的耦合度,提高了模块的相对独立性。
由于亲合度降低,从而提高了类的可复用率和系统的扩展性。
但是,过度使用迪米特法则会使系统产生大量的中介类,从而增加系统的复杂性,使模块之间的通信效率降低。所以,在釆用迪米特法则时需要反复权衡,确保高内聚和低耦合的同时,保证系统的结构清晰。
迪米特法则的实现方法
从迪米特法则的定义和特点可知,它强调以下两点:
从依赖者的角度来说,只依赖应该依赖的对象。
从被依赖者的角度说,只暴露应该暴露的方法。
所以,在运用迪米特法则时要注意以下 6 点。
在类的划分上,应该创建弱耦合的类。类与类之间的耦合越弱,就越有利于实现可复用的目标。
在类的结构设计上,尽量降低类成员的访问权限。
在类的设计上,优先考虑将一个类设置成不变类。
在对其他类的引用上,将引用其他对象的次数降到最低。
不暴露类的属性成员,而应该提供相应的访问器(set 和 get 方法)。
谨慎使用序列化(Serializable)功能
明星与经纪人的关系实例。
分析:明星由于全身心投入艺术,所以许多日常事务由经纪人负责处理,如与粉丝的见面会,与媒体公司的业务洽淡等。这里的经纪人是明星的朋友,而粉丝和媒体公司是陌生人,所以适合使用迪米特法则,其类图如下图所示。
package principle; public class LoDtest { public static void main(String[] args) { Agent agent=new Agent(); agent.setStar(new Star("林心如")); agent.setFans(new Fans("粉丝韩丞")); agent.setCompany(new Company("中国传媒有限公司")); agent.meeting(); agent.business(); } } //经纪人 class Agent { private Star myStar; private Fans myFans; private Company myCompany; public void setStar(Star myStar) { this.myStar=myStar; } public void setFans(Fans myFans) { this.myFans=myFans; } public void setCompany(Company myCompany) { this.myCompany=myCompany; } public void meeting() { System.out.println(myFans.getName()+"与明星"+myStar.getName()+"见面了。"); } public void business() { System.out.println(myCompany.getName()+"与明星"+myStar.getName()+"洽淡业务。"); } } //明星 class Star { private String name; Star(String name) { this.name=name; } public String getName() { return name; } } //粉丝 class Fans { private String name; Fans(String name) { this.name=name; } public String getName() { return name; } } //媒体公司 class Company { private String name; Company(String name) { this.name=name; } public String getName() { return name; } }
程序的运行结果如下:
粉丝韩丞与明星林心如见面了。 中国传媒有限公司与明星林心如洽淡业务。
到此,设计模式的六大原则就讲完了。
更多编程相关知识,请访问:编程教学!!
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!