Conception et architecture de logiciels - Modèles structurels

Si vous souhaitez en savoir plus à ce sujet, veuillez visiter mon site Web personnel: Espace élèves

Les modèles structurels décrivent comment organiser des classes ou des objets en structures plus grandes dans une certaine disposition. Tout comme les blocs de construction, des structures complexes et plus puissantes peuvent être formées grâce à la combinaison de blocs de construction simples

insérez la description de l'image ici

Les modèles structurels peuvent être divisés en modèles structurels de classe et en modèles structurels d'objet

  • Le modèle de structure de classe se soucie de la combinaison de classes. Plusieurs classes peuvent être combinées dans un système plus vaste. Dans le modèle de structure de classe, il n'y a généralement que des relations d'héritage et des relations d'implémentation.
  • Le modèle de structure d'objet se soucie de la combinaison de classes et d'objets, à travers la relation d'association, l'objet d'instance d'une autre classe est défini dans une classe, puis sa méthode est appelée via l'objet. Selon le "principe de réutilisation composite", essayez d'utiliser la relation d'association pour remplacer la relation d'héritage dans le système, de sorte que la plupart des modèles structurels sont des modèles structurels d'objet

Les modèles structurels sont divisés en sept types suivants :

  • Mode proxy
  • modèle d'adaptateur
  • motif décorateur
  • mode pont
  • mode d'apparence
  • mode combiné
  • Mode poids mouche

Un : mode proxy

Parfois, le client ne peut pas manipuler directement un objet, mais doit interagir avec cet objet, comme dans les deux situations suivantes :

  • Si l'objet est une grande image, il faudra beaucoup de temps pour l'afficher, alors vous devez créer une image Proxy pour remplacer l'image réelle
  • Si l'objet se trouve sur un serveur distant, l'utilisation directe de l'objet peut être lente en raison de la vitesse du réseau, nous pouvons alors utiliser Proxy pour remplacer cet objet en premier

Comment faire face à ce changement ? Comment fournir un mécanisme permettant une communication sans entrave entre deux objets avec lesquels il était à l'origine difficile d'interagir ? Comment empêcher la structure du système de changer facilement à mesure que les exigences changent ? C'est le mode mandataire.

Mode proxy : fournit un proxy pour d'autres objets afin de contrôler l'accès à cet objet.

Le mode agent est divisé en trois rôles :

  • Classe de thème abstrait : Déclarez les méthodes métier implémentées par le thème réel et les objets proxy via des interfaces ou des classes abstraites.
  • Classe de thème réel : réalise l'activité spécifique dans le thème abstrait, est l'objet réel représenté par l'objet proxy et est l'objet à référencer finalement.
  • Classe proxy : fournit la même interface que le thème réel et contient des références au thème réel à l'intérieur, qui peuvent accéder, contrôler ou étendre les fonctions du thème réel.

Les agents en Java sont divisés en différents types selon le temps de génération des classes d'agent :

  • Proxy statique : la classe de proxy proxy statique est générée au moment de la compilation
  • Proxy dynamique : la classe de proxy de proxy dynamique est générée dynamiquement lors de l'exécution de Java. Il existe deux types d'agents dynamiques :
    • Mandataire JDK
    • Proxy CGLib

1.1 : proxy statique

Voici un exemple de vente de billets dans une gare :

public class ProxyDemo1 {
    
    
    public static void main(String[] args) {
    
    
        ProxyPoint1 proxyPoint = new ProxyPoint1();
        proxyPoint.sell();
    }
}

// 售票接口
interface SellTickets1 {
    
    
    void sell();
}

// 火车站
class TrainStation1 implements SellTickets1 {
    
    
    @Override
    public void sell() {
    
    
        System.out.println("火车站卖票");
    }
}

// 代售点卖票
class ProxyPoint1 implements SellTickets1 {
    
    
    // 声明火车站类对象
    private TrainStation1 trainStation = new TrainStation1();

    @Override
    public void sell() {
    
    
        System.out.println("收取服务费");
        trainStation.sell();
    }
}

On peut voir à partir du code ci-dessus que la classe principale (agissant en tant que classe de test) accède directement à l'objet de classe ProxyPoint, c'est-à-dire que le ProxyPoint agit comme un intermédiaire entre l'objet d'accès et l'objet cible. Dans le même temps, la méthode de vente est également améliorée (les frais de service sont facturés par le point d'agence).

1.2 : Proxy dynamique

1.2.1 : Proxy JDK

Java fournit une classe proxy dynamique Proxy, qui fournit une méthode statique (méthode newProxyInstance) pour créer un objet proxy afin d'obtenir un objet proxy.

Le code pour améliorer la vente des billets à la gare est le suivant :

public class ProxyDemo2 {
    
    
    public static void main(String[] args) {
    
    
        ProxyFactory2 proxyFactory = new ProxyFactory2();
        SellTickets2 proxyObject = proxyFactory.getProxyObject();
        proxyObject.sell();
    }
}

// 售票接口
interface SellTickets2 {
    
    
    void sell();
}

// 火车站
class TrainStation2 implements SellTickets2 {
    
    
    @Override
    public void sell() {
    
    
        System.out.println("火车站卖票");
    }
}

// 代理工厂,用于创建代理对象
class ProxyFactory2 {
    
    
    private TrainStation2 station = new TrainStation2();

    public SellTickets2 getProxyObject() {
    
    
        return (SellTickets2)Proxy.newProxyInstance(
                station.getClass().getClassLoader(),
                station.getClass().getInterfaces(),
                (proxy, method, args) -> {
    
    
                    /*
                     * proxy:与Proxy.newProxyInstance返回值为同一对象
                     * method:对接口中的方法进行封装的method对象
                     * args:调用方法的实际参数
                     */
                    System.out.println("收取服务费");
                    return method.invoke(station, args);
                }
        );
    }
}

1.2.2 : Proxy CGLIB

Si l'interface SellTickets n'est pas définie, seule TrainStation (la classe de gare) est définie. De toute évidence, le proxy JDK ne peut pas être utilisé, car le proxy dynamique JDk nécessite que l'interface soit définie pour proxy l'interface.

CGLIB est un progiciel de génération de code puissant et performant. Il fournit des proxys pour les classes qui n'implémentent pas d'interfaces et fournit un bon complément aux proxys dynamiques du JDK.

CGLIB est un package fourni par un tiers, il faut donc introduire les coordonnées du package jar :

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>

Le code pour améliorer la vente des billets à la gare est le suivant :

public class ProxyDemo3 {
    
    
    public static void main(String[] args) {
    
    
        ProxyFactory3 proxyFactory3 = new ProxyFactory3();
        TrainStation3 proxyObject = proxyFactory3.getProxyObject();
        proxyObject.sell();
    }
}

// 火车站
class TrainStation3 {
    
    
    public void sell() {
    
    
        System.out.println("火车站卖票");
    }
}

// 代理对象工厂
class ProxyFactory3 implements MethodInterceptor {
    
    
    private TrainStation3 station = new TrainStation3();

    public TrainStation3 getProxyObject() {
    
    
        // 创建Enhancer对象,类似于JDK代理中的Proxy类
        Enhancer enhancer = new Enhancer();
        // 设置父类的字节码对象
        enhancer.setSuperClass(TrainStation3.class);
        // 设置回调函数
        enhancer.setCallback(this);
        // 创建代理对象
        return (proxyObject)enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects. MethodProxy methodProxy) {
    
    
        System.out.println("收取服务费");
        return method.invoke(station, objects);
    }
}

1.3 : Résumé

Comparaison de trois agents :

  • Proxy JDK et proxy CGLIB : utilisez CGLIB pour implémenter un proxy dynamique. La couche inférieure de CGLIB adopte le cadre de génération de bytecode ASM et utilise la technologie de bytecode pour générer des classes de proxy. Avant JDK1.6, c'est plus efficace que d'utiliser la réflexion Java. La seule chose à noter est que CGLIB ne peut pas proxy des classes ou des méthodes déclarées comme final, car le principe de CGLIB est de générer dynamiquement des sous-classes de la classe proxy. Après que JDK1.6, JDK1.7 et JDK1.8 ont progressivement optimisé le proxy dynamique JDK, l'efficacité du proxy JDK est supérieure à celle du proxy CGLIB lorsque le nombre d'appels est faible. Uniquement lorsqu'un grand nombre d'appels sont effectués, JDK1 .6 et JDK1.7 est un peu moins efficace que le proxy CGLIB, mais en ce qui concerne JDK1.8, le proxy JDK est plus efficace que le proxy CGLib. Donc, s'il y a une interface, utilisez le proxy dynamique JDK, s'il n'y a pas d'interface, utilisez le proxy CGLIB.
  • Proxy dynamique et proxy statique : par rapport au proxy statique, le principal avantage du proxy dynamique est que toutes les méthodes déclarées dans l'interface sont transférées vers une méthode centralisée de processeur d'invocation (InvocationHandler.invoke). De cette façon, lorsqu'il existe un grand nombre de méthodes d'interface, nous pouvons les gérer de manière flexible, sans avoir besoin de transférer chaque méthode comme un proxy statique. Si l'interface ajoute une méthode, en plus de toutes les classes d'implémentation devant implémenter cette méthode en mode proxy statique, toutes les classes proxy doivent également implémenter cette méthode. Augmentation de la complexité de la maintenance du code. Ce problème ne se produit pas avec les proxys dynamiques.

Avantages du mode proxy :

  • Le mode proxy agit comme un intermédiaire entre le client et l'objet cible et protège l'objet cible
  • Un objet proxy peut étendre les fonctionnalités d'un objet cible
  • Le mode proxy peut séparer le client de l'objet cible, réduisant dans une certaine mesure le couplage du système

Inconvénients du mode proxy :

  • augmente la complexité du système

scènes à utiliser :

  • Proxy distant : le service local demande le service distant via le réseau. Afin de parvenir à une communication locale à distante, nous devons implémenter une communication réseau et gérer les éventuelles exceptions. Pour une bonne conception du code et une bonne maintenabilité, nous cachons la partie communication réseau et n'exposons qu'une interface au service local, à travers laquelle les fonctions fournies par le service distant sont accessibles sans trop prêter attention aux détails de la partie communication.
  • Proxy virtuel : informations sur les objets en cache lorsque des objets coûteux doivent être créés
  • Proxy de protection : contrôle l'accès à l'objet d'origine. Différents niveaux d'accès peuvent être accordés à différents utilisateurs si vous le souhaitez.
  • Smart Reference Proxy (Smart Reference Proxy) : Lorsqu'un objet est référencé, il fournit quelques opérations supplémentaires, comme l'enregistrement du trafic et des temps de visites, etc.
  • Proxy pare-feu : lorsque vous configurez votre navigateur pour utiliser la fonction proxy, le pare-feu transmet la demande de votre navigateur à Internet ; lorsque Internet renvoie une réponse, le serveur proxy la transmet à votre navigateur.

Deux : Mode adaptateur

Si vous voyagez dans des pays européens, leurs prises sont à l'extrême gauche de l'image ci-dessous, qui est la norme européenne. Et la prise que nous utilisons est celle à l'extrême droite de l'image ci-dessous. Par conséquent, nos ordinateurs portables et téléphones portables ne peuvent pas être directement chargés localement. Nous avons donc besoin d'un convertisseur de prise.Le premier côté du convertisseur est branché sur la prise locale, et le second côté est à charger, afin que notre prise puisse être utilisée localement. Il existe de nombreux exemples de ce type dans la vie, les chargeurs de téléphones portables (convertissant 220x en tension 5x), les lecteurs de cartes, etc., utilisent en fait le mode adaptateur.

insérez la description de l'image ici

Modèle d'adaptateur : convertissez l'interface d'une classe en une autre interface souhaitée par le client, afin que les classes qui ne peuvent pas fonctionner ensemble en raison d'une incompatibilité d'interface puissent fonctionner ensemble.

Applications:

  • Utiliser une classe existante dont l'interface ne répond pas aux exigences
  • Créer une classe réutilisable qui peut fonctionner avec d'autres classes non liées ou des classes imprévues (c'est-à-dire des classes dont les interfaces ne sont pas nécessairement compatibles)
  • Utilisez certaines sous-classes existantes, mais il n'est pas possible de créer des sous-classes pour correspondre aux interfaces respectives. L'adaptateur d'objet peut adapter son interface de classe parent

insérez la description de l'image ici

Le modèle d'adaptateur contient les rôles principaux suivants :

  • Interface cible : l'interface attendue par l'activité système actuelle, qui peut être une classe abstraite ou une interface
  • Classe d'adaptateur : il s'agit de l'interface de composant dans la bibliothèque de composants existante à accéder et à adapter
  • Classe d'adaptateur : il s'agit d'un convertisseur qui convertit l'interface de l'adaptateur en interface cible en héritant ou en référençant l'objet adaptateur, permettant aux clients d'accéder à l'adaptateur dans le format de l'interface cible

Le mode adaptateur est divisé en mode adaptateur de classe et mode adaptateur d'objet. Le premier a un degré de couplage entre les classes plus élevé que le second et nécessite que les programmeurs comprennent la structure interne des composants associés dans la bibliothèque de composants existante, il y a donc relativement peu d'applications. .

2.1 : Adaptateur de classe

Méthode d'implémentation : définir une classe d'adaptateur pour réaliser l'interface métier du système actuel, et en même temps hériter des composants existants dans la bibliothèque de composants existante.

Par exemple, un ordinateur existant ne peut lire que la carte SD, mais si vous souhaitez lire le contenu de la carte TF, vous devez utiliser le mode adaptateur. Créez un lecteur de carte pour lire le contenu de la carte TF. code afficher comme ci-dessous:

public class AdapterDemo1 {
    
    
    public static void main(String[] args) {
    
    
        Computer computer = new Computer();
        String msg = computer.readSD(new SDAdapterTF());
        System.out.println(msg);
    }
}

interface TFCard {
    
    
    String readTF();

    void writeTF(String msg);
}

// 具体TF卡
class TFCardImpl implements TFCard {
    
    
    @Override
    public String readTF() {
    
    
        return "hello TFCard";
    }

    @Override
    public void writeTF(String msg) {
    
    
        System.out.println("TFCard write msg:" + msg);
    }
}

// 目标接口
interface SDCard {
    
    
    String readSD();

    void writeSD(String msg);
}

// 具体SD卡
class SDCardImpl implements SDCard {
    
    

    @Override
    public String readSD() {
    
    
        return "hello SDCard";
    }

    @Override
    public void writeSD(String msg) {
    
    
        System.out.println("SDCard write msg:" + msg);
    }
}

// 计算机类
class Computer {
    
    
    // 从SD卡中读取数据
    public String readSD(SDCard sdCard) {
    
    
        return sdCard.readSD();
    }
}

// 适配器类
class SDAdapterTF extends TFCardImpl implements SDCard {
    
    
    @Override
    public String readSD() {
    
    
        return readTF();
    }

    @Override
    public void writeSD(String msg) {
    
    
        writeTF(msg);
    }
}

Le modèle d'adaptateur de classe viole le principe de réutilisation compositionnelle. Un adaptateur de classe est disponible si la classe client a une spécification d'interface, et pas autrement.

2.2 : Adaptateur d'objet

Méthode de réalisation : le mode adaptateur d'objet peut introduire les composants implémentés dans la bibliothèque de composants existante dans la classe d'adaptateur, qui réalise simultanément l'interface métier du système actuel.

Pour améliorer le boîtier du lecteur de carte ci-dessus, le code est le suivant :

public class AdapterDemo2 {
    
    
    public static void main(String[] args) {
    
    
        Computer2 computer = new Computer2();
        String msg = computer.readSD(new SDAdapterTF2(new TFCardImpl2()));
        System.out.println(msg);
    }
}

interface TFCard2 {
    
    
    String readTF();

    void writeTF(String msg);
}

// 具体TF卡
class TFCardImpl2 implements TFCard2 {
    
    
    @Override
    public String readTF() {
    
    
        return "hello TFCard";
    }

    @Override
    public void writeTF(String msg) {
    
    
        System.out.println("TFCard write msg:" + msg);
    }
}

// 目标接口
interface SDCard2 {
    
    
    String readSD();

    void writeSD(String msg);
}

// 具体SD卡
class SDCardImpl2 implements SDCard2 {
    
    

    @Override
    public String readSD() {
    
    
        return "hello SDCard";
    }

    @Override
    public void writeSD(String msg) {
    
    
        System.out.println("SDCard write msg:" + msg);
    }
}

// 计算机类
class Computer2 {
    
    
    // 从SD卡中读取数据
    public String readSD(SDCard2 sdCard) {
    
    
        return sdCard.readSD();
    }
}

// 适配器类
class SDAdapterTF2 implements SDCard2 {
    
    
    // 声明适配者类
    private final TFCard2 tfCard;

    public SDAdapterTF2(TFCard2 tfCard) {
    
    
        this.tfCard = tfCard;
    }

    @Override
    public String readSD() {
    
    
        return tfCard.readTF();
    }

    @Override
    public void writeSD(String msg) {
    
    
        tfCard.writeTF(msg);
    }
}

Trois : Mode décorateur

Commençons par un exemple de restauration rapide. Les restaurants de restauration rapide proposent des plats rapides tels que des nouilles frites et du riz frit. Des plats d'accompagnement supplémentaires tels que des œufs, du jambon et du bacon peuvent être ajoutés. Bien sûr, des plats d'accompagnement supplémentaires doivent être facturés. Le prix de chaque plat d'accompagnement est généralement différent, le calcul du prix total sera donc plus gênant.

Le motif décorateur (Decorator Pattern) est un motif qui ajoute dynamiquement certaines responsabilités (c'est-à-dire ajoute ses fonctions supplémentaires) à l'objet sans modifier la structure de l'objet.

Rôles dans le modèle de décorateur

  • Rôle de composant abstrait : définir une interface abstraite pour standardiser les objets prêts à recevoir des responsabilités supplémentaires
  • Rôles de composants concrets : implémentez des composants abstraits et ajoutez-leur des responsabilités via des rôles de décoration
  • Rôle de décoration abstraite : hériter ou implémenter des composants abstraits, et contenir des instances de composants concrets, et peut étendre les fonctions des composants concrets à travers ses sous-classes
  • Rôle de décoration concrète : implémentez des méthodes connexes de décoration abstraite et ajoutez des responsabilités supplémentaires aux objets composants concrets

L'exemple de code est le suivant :

public class DecoratorDemo1 {
    
    
    public static void main(String[] args) {
    
    
        // 点一份炒饭
        FastFood food = new FriedRice();
        System.out.println(food.getDesc() + "的价格:" + food.cost());

        // 再加一份鸡蛋
        food = new Egg(food);
        System.out.println(food.getDesc() + "的价格:" + food.cost());

        // 再加一份培根
        food = new Bacon(food);
        System.out.println(food.getDesc() + "的价格:" + food.cost());
    }
}

abstract class FastFood {
    
    
    private float price;
    private String desc;

    public FastFood() {
    
    }

    public FastFood(float price, String desc) {
    
    
        this.price = price;
        this.desc = desc;
    }

    public float getPrice() {
    
    
        return price;
    }

    public void setPrice(float price) {
    
    
        this.price = price;
    }

    public String getDesc() {
    
    
        return desc;
    }

    public void setDesc(String desc) {
    
    
        this.desc = desc;
    }

    public abstract float cost();
}

// 炒饭
class FriedRice extends FastFood {
    
    
    public FriedRice() {
    
    
        super(10, "炒饭");
    }

    @Override
    public float cost() {
    
    
        return getPrice();
    }
}

// 炒面
class FriedNoodles extends FastFood {
    
    
    public FriedNoodles() {
    
    
        super(12, "炒面");
    }

    @Override
    public float cost() {
    
    
        return getPrice();
    }
}

// 装饰类
abstract class Garnish extends FastFood {
    
    
    // 声明快餐类的变量
    private FastFood fastFood;

    public FastFood getFastFood() {
    
    
        return fastFood;
    }

    public void setFastFood(FastFood fastFood) {
    
    
        this.fastFood = fastFood;
    }

    public Garnish(FastFood fastFood, float price, String desc) {
    
    
        super(price, desc);
        this.fastFood = fastFood;
    }
}

// 配料类(鸡蛋)
class Egg extends Garnish {
    
    

    public Egg(FastFood fastFood) {
    
    
        super(fastFood, 1, "鸡蛋");
    }

    @Override
    public float cost() {
    
    
        // 计算价格
        return getPrice() + getFastFood().cost();
    }

    @Override
    public String getDesc() {
    
    
        return super.getDesc() + getFastFood().getDesc();
    }
}

// 配料类(培根)
class Bacon extends Garnish {
    
    

    public Bacon(FastFood fastFood) {
    
    
        super(fastFood, 2, "培根");
    }

    @Override
    public float cost() {
    
    
        // 计算价格
        return getPrice() + getFastFood().cost();
    }

    @Override
    public String getDesc() {
    
    
        return super.getDesc() + getFastFood().getDesc();
    }
}

Avantages du motif décorateur :

  • Le modèle de décorateur peut apporter des fonctions d'extension plus flexibles que l'héritage et est plus pratique à utiliser. Vous pouvez obtenir divers résultats avec différents états de comportement en combinant différents objets décorateurs. Le pattern décorateur est plus extensible que l'héritage, et il suit parfaitement le principe d'ouverture et de fermeture. L'héritage est une responsabilité supplémentaire statique, et le décorateur est une responsabilité supplémentaire dynamique.
  • La classe de décoration et la classe décorée peuvent se développer indépendamment sans se coupler. Le mode de décoration est un mode d'héritage alternatif. Le mode de décoration peut étendre dynamiquement la fonction d'une classe d'implémentation.

scènes à utiliser :

  • Il est utilisé lorsque le système ne peut pas être étendu par héritage ou que l'héritage n'est pas propice à l'expansion et à la maintenance du système. Il existe deux grandes catégories de situations dans lesquelles l'héritage ne peut pas être utilisé :
    • La première catégorie est qu'il existe un grand nombre d'extensions indépendantes dans le système. Pour prendre en charge chaque combinaison, un grand nombre de sous-catégories sera généré, ce qui entraînera une croissance explosive du nombre de sous-catégories.
    • La deuxième catégorie est due au fait que les définitions de classe ne peuvent pas être héritées (comme les classes finales)
  • Ajoutez des responsabilités à des objets individuels de manière dynamique et transparente sans affecter les autres objets.
  • Lorsque les exigences fonctionnelles de l'objet peuvent être ajoutées dynamiquement, et peuvent également être révoquées dynamiquement.

Quatre : Mode pont

Il est maintenant nécessaire de créer des graphiques différents, et chaque graphique peut avoir des couleurs différentes. Nous pouvons utiliser l'héritage pour concevoir la relation des classes :
insérez la description de l'image ici

On peut constater qu'il y a beaucoup de classes, si on ajoute une autre forme ou une autre couleur, il faut créer plus de classes.

Imaginez, dans un système avec de multiples dimensions qui peuvent changer, l'utilisation de l'héritage provoquera une explosion de classe et une expansion inflexible. Chaque fois qu'une implémentation concrète est ajoutée à une dimension, plusieurs sous-classes sont ajoutées. Afin de concevoir le système de manière plus flexible, nous pouvons envisager d'utiliser le mode pont à ce stade.

Définition du mode Bridge : Séparez l'abstraction de l'implémentation afin qu'elles puissent varier indépendamment. Elle est mise en œuvre en remplaçant la relation d'héritage par la relation de composition, réduisant ainsi le degré de couplage des deux dimensions variables d'abstraction et de mise en œuvre.

Le mode Bridge comprend les rôles principaux suivants :

  • Rôle d'abstraction : définit une classe abstraite et contient une référence à un objet d'implémentation.
  • Rôle d'abstraction étendue (abstraction raffinée) : il s'agit d'une sous-classe du rôle d'abstraction, implémente la méthode métier dans la classe parente et appelle la méthode métier dans le rôle réalisé via la relation de composition.
  • Rôle d'implémenteur : Définissez l'interface du rôle d'implémenteur, qui peut être appelé par le rôle abstrait étendu. Rôle d'implémenteur concret : Donne l'implémentation concrète de l'interface du rôle d'implémenteur.

Ce qui suit est un exemple. Il est nécessaire de développer un lecteur vidéo multiplateforme capable de lire des fichiers vidéo dans différents formats sur différentes plates-formes de système d'exploitation (telles que Windows, Mac, Linux, etc.) Les formats vidéo courants incluent RMVB, AVI, WMV, etc. Le lecteur contient deux dimensions, adaptées à une utilisation en mode pont.

public class BridgeDemo {
    
    
    public static void main(String[] args) {
    
    
        // 创建Mac系统对象
        OperatingSystem system = new Mac(new AviFile());
        system.play("三体");
    }
}

// 视频文件接口
interface VideoFile {
    
    
    // 解码内容
    void decode(String fileName);
}

// Avi格式视频文件类
class AviFile implements VideoFile {
    
    
    @Override
    public void decode(String fileName) {
    
    
        System.out.println("avi视频文件:" + fileName);
    }
}

// Rmvb格式视频文件类
class RmvbFile implements VideoFile {
    
    
    @Override
    public void decode(String fileName) {
    
    
        System.out.println("Rmvb视频文件:" + fileName);
    }
}

// 抽象操作系统类
abstract class OperatingSystem {
    
    
    // 声明videoFile变量
    protected VideoFile videoFile;

    public OperatingSystem(VideoFile videoFile) {
    
    
        this.videoFile = videoFile;
    }

    public abstract void play(String fileName);
}

// Windows操作系统
class Windows extends OperatingSystem {
    
    
    public Windows(VideoFile videoFile) {
    
    
        super(videoFile);
    }

    @Override
    public void play(String fileName) {
    
    
        videoFile.decode(fileName);
    }
}

// Mac操作系统
class Mac extends OperatingSystem {
    
    
    public Mac(VideoFile videoFile) {
    
    
        super(videoFile);
    }

    @Override
    public void play(String fileName) {
    
    
        videoFile.decode(fileName);
    }
}

Avantages du mode pont :

  • L'évolutivité du système est améliorée et l'une des deux dimensions changeantes peut être arbitrairement étendue sans modifier le système d'origine. Par exemple : s'il existe encore un fichier vidéo de type wmv, il suffit de définir une autre classe pour implémenter l'interface videoFile, et les autres classes n'ont pas besoin d'être modifiées.
  • Les détails de mise en œuvre sont transparents pour les clients

scènes à utiliser :

  • Lorsqu'une classe a deux dimensions qui changent indépendamment et que les deux dimensions doivent être étendues.
  • Lorsqu'un système ne souhaite pas utiliser l'héritage ou lorsque le nombre de classes système augmente considérablement en raison de l'héritage à plusieurs niveaux.
  • Lorsqu'un système doit ajouter plus de flexibilité entre les rôles abstraits et concrets des composants. Évitez d'établir des liens d'héritage statiques entre les deux niveaux, et ils peuvent établir une relation d'association au niveau abstrait via le mode pont.

Cinq : mode d'apparence

Certaines personnes ont peut-être négocié des actions, mais en fait, la plupart d'entre elles ne savent pas grand-chose. Il est facile de perdre de l'argent en actions sans une connaissance suffisante des valeurs mobilières. Lorsque vous commencerez à négocier des actions, vous penserez certainement que si vous avez un assistant compétent Eh bien, en fait, le fonds est un bon assistant. Il existe de nombreux fonds dans Alipay. Il rassemble les fonds dispersés des investisseurs, les gère par des gestionnaires professionnels et investit dans des actions, des obligations, des devises et autres Investissement du fonds Le revenu du jeton appartient au détenteur et l'agence de gestion prélève un certain pourcentage de frais de gestion de garde.

Le motif de façade, également appelé motif de façade, est un motif qui facilite l'accès à ces sous-systèmes en fournissant une interface cohérente pour plusieurs sous-systèmes complexes. Ce mode a une interface unifiée avec le monde extérieur et le programme d'application externe n'a pas besoin de se soucier des détails spécifiques du sous-système interne, ce qui réduira considérablement la complexité du programme d'application et améliorera la maintenabilité du programme. Le mode d'apparence est une application typique de la loi de Demeter.

insérez la description de l'image ici

Le motif Façade comprend principalement les rôles principaux suivants :

  • Rôle de façade : fournir une interface commune pour plusieurs sous-systèmes.
  • Rôle de sous-système (sous-système): implémente certaines fonctions du système et les clients peuvent y accéder via le rôle de façade.

Voici un exemple de question : Le grand-père de Xiao Ming a 60 ans et vit seul à la maison : il doit allumer les lumières, la télévision et la climatisation à chaque fois ; éteindre les lumières, éteindre la télévision et éteindre le climatiseur quand il dort, c'est plus gênant à faire fonctionner. Xiao Ming a donc acheté un haut-parleur intelligent pour son grand-père, qui peut contrôler directement l'ouverture et la fermeture de ces appareils électroménagers intelligents par la voix.

code afficher comme ci-dessous:

public class FacadeDemo {
    
    
    public static void main(String[] args) {
    
    
        SmartApplicationFacade facade = new SmartApplicationFacade();
        facade.say("打开家电");
        System.out.println("-----------------");
        facade.say("关闭家电");
    }
}

// 家具——灯
class Light {
    
    
    public void on() {
    
    
        System.out.println("打开灯。。。");
    }

    public void off() {
    
    
        System.out.println("关闭灯。。。");
    }
}

// 家具——电视
class TV {
    
    
    public void on() {
    
    
        System.out.println("打开电视。。。");
    }

    public void off() {
    
    
        System.out.println("关闭电视。。。");
    }
}

// 外观角色——智能音箱
class SmartApplicationFacade {
    
    
    private Light light;
    private TV tv;

    public SmartApplicationFacade() {
    
    
        light = new Light();
        tv = new TV();
    }

    private void on() {
    
    
        light.on();
        tv.on();
    }

    private void off() {
    
    
        light.off();
        light.off();
    }

    public void say(String msg) {
    
    
        if (msg.contains("打开")) {
    
    
            on();
        } else if (msg.contains("关闭")) {
    
    
            off();
        } else {
    
    
            System.out.println("我还听不懂");
        }
    }
}

Avantages du mode façade :

  • Le degré de couplage entre le sous-système et le client est réduit, de sorte que le changement de sous-système n'affectera pas la classe client qui l'appelle.
  • Les composants du sous-système sont protégés du client, ce qui réduit le nombre d'objets gérés par le client et facilite l'utilisation du sous-système.

Inconvénients du mode façade :

  • Il n'est pas conforme au principe d'ouverture et de fermeture, et il est très gênant de le modifier

scènes à utiliser :

  • Lors de la construction de systèmes hiérarchiques, l'utilisation du modèle de façade pour définir des points d'entrée pour chaque couche d'un sous-système simplifie les dépendances entre les sous-systèmes.
  • Lorsqu'un système complexe comporte de nombreux sous-systèmes, le mode d'apparence peut concevoir une interface simple pour que le système soit accessible par le monde extérieur.
  • Lorsqu'il existe une connexion importante entre le client et plusieurs sous-systèmes, l'introduction du motif de façade peut les séparer, améliorant ainsi l'indépendance et la portabilité des sous-systèmes.

Six : Mode combiné

Dans la figure ci-dessous, nous pouvons le considérer comme un système de fichiers.Pour une telle structure, nous l'appelons une arborescence. Dans la structure arborescente, l'arbre entier peut être parcouru en appelant une certaine méthode.Lorsque nous trouvons un nœud feuille, nous pouvons effectuer des opérations connexes sur le nœud feuille. Cet arbre peut être compris comme un grand conteneur, qui contient de nombreux objets membres, et ces objets membres peuvent être des objets conteneurs ou des objets feuilles. Cependant, en raison de la différence de fonction entre les objets conteneurs et les objets feuilles, nous devons faire la distinction entre les objets conteneurs et les objets feuilles lors de l'utilisation, mais cela causera des problèmes inutiles aux clients. En tant que client, il espère toujours pouvoir traiter les objets conteneurs. et les objets feuilles de manière cohérente.

insérez la description de l'image ici

Le mode composite, également appelé mode partie-tout, est utilisé pour traiter un groupe d'objets similaires comme un seul objet. Le pattern Composite compose des objets selon une structure arborescente, utilisée pour représenter des hiérarchies de partie et de tout. Ce type de modèle de conception est un modèle structurel, qui crée une structure arborescente de groupes d'objets.

Le mode combiné comprend principalement trois rôles :

  • Nœud racine abstrait (composant) : définit les méthodes et propriétés communes des objets à tous les niveaux du système et peut prédéfinir certains comportements et propriétés par défaut.
  • Composite : définissez le comportement d'un nœud de branche, stockez les nœuds enfants et combinez les nœuds de branche et les nœuds feuille pour former une structure arborescente.
  • Nœud feuille (Leaf) : objet nœud feuille, il n'y a pas de branche en dessous et c'est la plus petite unité de traversée de la hiérarchie du système.

Voici un cas : Comme le montre la figure ci-dessous, lorsque nous visitons d'autres systèmes de gestion, nous pouvons souvent voir des menus similaires. Un menu peut contenir des éléments de menu (les éléments de menu font référence à des éléments de menu qui ne contiennent plus d'autre contenu), et peut également contenir des menus avec d'autres éléments de menu, il est donc approprié d'utiliser le mode combinaison pour décrire le menu. Imprimez tous les menus qu'il contient! et les noms des éléments de menu.
insérez la description de l'image ici

code afficher comme ci-dessous:

public class CombinationDemo {
    
    
    public static void main(String[] args) {
    
    
        MenuComponent menu00 = new Menu("系统管理", 0);
        MenuComponent menu10 = new Menu("菜单管理", 1);
        MenuComponent menu11 = new Menu("权限配置", 1);
        MenuComponent menu12 = new Menu("角色管理", 1);
        menu00.add(menu10);
        menu00.add(menu11);
        menu00.add(menu12);

        menu10.add(new MenuItem("页面访问", 2));
        menu10.add(new MenuItem("展开菜单", 2));
        menu10.add(new MenuItem("编辑菜单", 2));
        menu10.add(new MenuItem("删除菜单", 2));
        menu10.add(new MenuItem("新增菜单", 2));

        menu11.add(new MenuItem("页面访问", 2));
        menu11.add(new MenuItem("保存提交", 2));

        menu12.add(new MenuItem("页面访问", 2));
        menu12.add(new MenuItem("新增角色", 2));
        menu12.add(new MenuItem("修改角色", 2));

        menu00.print();
    }
}

// 菜单组件:属于抽象根节点
abstract class MenuComponent {
    
    
    // 菜单组件的名称
    protected String name;
    // 菜单组件的层级
    protected int level;

    // 添加子菜单
    public void add(MenuComponent menuComponent) {
    
    
        throw new UnsupportedOperationException();
    }

    // 移除子菜单
    public void remove(MenuComponent menuComponent) {
    
    
        throw new UnsupportedOperationException();
    }

    // 获取指定的子菜单
    public MenuComponent getChild(int index) {
    
    
        throw new UnsupportedOperationException();
    }

    // 获取菜单或者菜单项的名称
    public String getName() {
    
    
        return name;
    }

    // 打印菜单名称的方法(包含子菜单和子菜单项)
    public abstract void print();
}

// 菜单类:属于树枝节点
class Menu extends MenuComponent {
    
    
    // 菜单可以有多个子菜单或者子菜单项
    private final List<MenuComponent> menuComponentList = new ArrayList<>();

    public Menu(String name, int level) {
    
    
        this.name = name;
        this.level = level;
    }

    @Override
    public void add(MenuComponent menuComponent) {
    
    
        menuComponentList.add(menuComponent);
    }

    @Override
    public void remove(MenuComponent menuComponent) {
    
    
        menuComponentList.remove(menuComponent);
    }

    @Override
    public MenuComponent getChild(int index) {
    
    
        return menuComponentList.get(index);
    }

    @Override
    public void print() {
    
    
        for (int i = 0; i < level; i++) {
    
    
            System.out.print("--");
        }

        // 打印菜单名称
        System.out.println(name);

        // 打印子菜单或者子菜单项名称
        for (MenuComponent component : menuComponentList) {
    
    
            component.print();
        }
    }
}

// 菜单项类:属于叶子节点
class MenuItem extends MenuComponent {
    
    
    public MenuItem(String name, int level) {
    
    
        this.name = name;
        this.level = level;
    }

    @Override
    public void print() {
    
    
        for (int i = 0; i < level; i++) {
    
    
            System.out.print("--");
        }

        System.out.println(name);
    }
}

Lors de l'utilisation du mode de combinaison, selon la forme de définition de la classe de composants abstraits, nous pouvons diviser le mode de combinaison en deux formes : le mode de combinaison transparent et le mode de combinaison sécurisé.

  • Mode composite transparent
    • Toutes les méthodes utilisées pour gérer les objets membres sont déclarées dans le rôle du nœud racine abstrait. Par exemple, dans l'exemple, Menucomponent déclare les addméthodes remove, et getChild. L'avantage est de garantir que toutes les classes de composants ont la même interface. Le mode composite transparent est également la forme standard du mode composite.
    • L'inconvénient est qu'il n'est pas assez sûr, car les objets feuilles et les objets conteneurs sont essentiellement différents. Les objets feuilles ne peuvent pas avoir d'objets au niveau suivant, c'est-à-dire qu'ils ne peuvent pas contenir d'objets membres, il est donc inutile de fournir des méthodes telles que , et Il add()n'yremove() aura pas d'erreur pendant la phase de compilation, mais des erreurs peuvent se produire si ces méthodes sont appelées pendant la phase d'exécution (si aucun code de gestion d'erreur correspondant n'est fourni)
  • Mode composite de sécurité
    • Aucune méthode de gestion des objets membres n'est déclarée dans le rôle de composant abstrait, mais ces méthodes sont déclarées et implémentées dans la classe Menu du nœud de branche.
    • L'inconvénient est qu'il n'est pas assez transparent, car le composant feuille et le composant conteneur ont des méthodes différentes, et les méthodes utilisées pour gérer les objets membres dans le composant conteneur ne sont pas définies dans la classe du composant abstrait, de sorte que le client ne peut pas complètement programmer contre l'abstraction et doivent être traités différemment Composants feuille et composants conteneur.

Avantages du motif composite :

  • Le mode composite peut clairement définir des objets complexes hiérarchiques, exprimant tout ou partie de la hiérarchie de l'objet, ce qui permet au client d'ignorer les différences de hiérarchie et facilite le contrôle de toute la hiérarchie.
  • Le client peut utiliser une structure composite ou un seul objet de manière cohérente, qu'il s'agisse d'un objet unique ou de la structure composite entière, ce qui simplifie le code client.
  • Il est très pratique d'ajouter de nouveaux nœuds de branche et nœuds feuilles en mode combinaison sans aucune modification de la bibliothèque de classes existante, qui est conforme au "principe d'ouverture et de fermeture".
  • Le mode combinaison offre une solution flexible pour la réalisation orientée objet de la structure arborescente. Grâce à la combinaison récursive des nœuds feuille et des nœuds branche, une structure arborescente complexe peut être formée, mais le contrôle de la structure arborescente est très simple.

Scénario d'utilisation : Le mode combiné est né en réponse à l'arborescence, donc le scénario d'utilisation du mode combiné est là où l'arborescence apparaît. Par exemple : affichage du répertoire de fichiers, présentation du répertoire à plusieurs niveaux et autres opérations sur les données de la structure arborescente.

Seven : mode poids mouche

Mode poids plume : utilisez la technologie de partage pour prendre en charge efficacement la réutilisation d'un grand nombre d'objets à grain fin. Il réduit considérablement le nombre d'objets qui doivent être créés en partageant des objets existants et évite la surcharge d'un grand nombre d'objets similaires, améliorant ainsi l'utilisation des ressources système.

Le mode Flyweight a les deux états suivants : L'implémentation du mode Flyweight consiste à distinguer les deux états dans l'application et à externaliser l'état externe.

  • État interne, une partie partageable qui ne change pas lorsque l'environnement change.
  • L'état externe fait référence à la partie non partageable qui change à mesure que l'environnement change.

Le mode poids mouche a principalement les rôles suivants :

  • Rôle de poids mouche abstrait (Flyweight) : généralement une interface ou une classe abstraite, qui déclare les méthodes publiques de la classe de poids mouche spécifique dans la classe de poids mouche abstraite. Ces méthodes peuvent fournir les données internes (état interne) de l'objet poids mouche au monde extérieur. , et en même temps les données externes (état externe) peuvent également être définies via ces méthodes.
  • Rôle de poids mouche concret : il implémente la classe abstraite de poids mouche, appelée objet poids mouche ; il fournit un espace de stockage pour l'état interne dans la classe de poids mouche concret. Habituellement, nous pouvons combiner le mode singleton pour concevoir des classes de poids mouche spécifiques et fournir des objets de poids mouche uniques pour chaque classe de poids mouche spécifique.
  • Rôle poids mouche non partageable : toutes les sous-classes de la classe poids mouche abstraite n'ont pas besoin d'être partagées, et les sous-classes qui ne peuvent pas être partagées peuvent être conçues comme des classes poids mouche concrètes non partagées ; lorsqu'une classe poids mouche concrète non partagée est requise, les objets peuvent être créés directement par instanciation.
  • Rôle Flyweight Factory : responsable de la création et de la gestion des rôles de poids mouche. Lorsqu'un objet client demande un objet poids mouche, la fabrique de poids mouche vérifie s'il existe un objet poids mouche qui répond aux exigences du système, et s'il existe, il sera fourni au client ; s'il n'existe pas, un nouvel objet poids mouche sera créé.

Voici un exemple de question : L'image ci-dessous est un bloc dans le jeu Tetris bien connu. Si dans le jeu Tetris, chaque bloc différent est un objet d'instance, ces objets prendront beaucoup d'espace mémoire. Utilisons Share The meta -pattern est implémenté.

insérez la description de l'image ici

code afficher comme ci-dessous:

public class FlyweightDemo {
    
    
    public static void main(String[] args) {
    
    
        BoxFactory instance = BoxFactory.getInstance();
        instance.getShape("I").display("灰色");
        instance.getShape("L").display("红色");
        instance.getShape("O").display("绿色");
    }
}

// 抽象享元角色
abstract class AbstractBox {
    
    
    // 获取图形的方法
    public abstract String getShape();

    // 显示图形及颜色
    public void display(String color) {
    
    
        System.out.println("方块形状:" + getShape() + ",颜色:" + color);
    }
}

// 具体享元——I图形类
class IBox extends AbstractBox {
    
    
    @Override
    public String getShape() {
    
    
        return "I";
    }
}

// 具体享元——L图形类
class LBox extends AbstractBox {
    
    
    @Override
    public String getShape() {
    
    
        return "L";
    }
}

// 具体享元——O图形类
class OBox extends AbstractBox {
    
    
    @Override
    public String getShape() {
    
    
        return "O";
    }
}

// 工厂类:将该类设置为单例
class BoxFactory {
    
    
    private HashMap<String, AbstractBox> map;
    private static BoxFactory factory = new BoxFactory();

    // 在构造方法中进行初始化操作
    private BoxFactory() {
    
    
        map = new HashMap<>();
        map.put("I", new IBox());
        map.put("L", new LBox());
        map.put("O", new OBox());
    }

    // 获取实例
    public static BoxFactory getInstance() {
    
    
        return factory;
    }

    // 根据名称获取图形对象
    public AbstractBox getShape(String name) {
    
    
        return map.get(name);
    }
}

Avantages du mode poids mouche :

  • Réduisez considérablement le nombre d'objets similaires ou identiques en mémoire, économisez les ressources système et améliorez les performances du système
  • L'état externe en mode Flyweight est relativement indépendant et n'affecte pas l'état interne

Inconvénients du mode poids mouche :

  • Afin de rendre l'objet partageable, il est nécessaire d'externaliser une partie de l'état de l'objet masselotte, de séparer l'état interne et l'état externe, et de compliquer la logique du programme

scènes à utiliser :

  • Un système comporte un grand nombre d'objets identiques ou similaires, ce qui entraîne une grande consommation de mémoire.
  • La plupart de l'état d'un objet peut être externalisé, et ces états externes peuvent être transmis à l'objet.
  • Lorsque vous utilisez le mode Flyweight, il est nécessaire de maintenir un pool Flyweight pour stocker les objets Flyweight, ce qui consomme certaines ressources système. Par conséquent, il est utile d'utiliser le mode Flyweight lorsque les objets Flyweight doivent être réutilisés plusieurs fois.

Acho que você gosta

Origin blog.csdn.net/tongkongyu/article/details/127703517
Recomendado
Clasificación