[JAVA] Réaliser une table de décision sans utiliser le branchement conditionnel

1.Tout d'abord

Cette fois, j'ai eu l'occasion de mettre en œuvre les spécifications organisées dans la table de décision, je voudrais donc expliquer comment implémenter un programme qui fonctionne selon la définition.

Une table de décision est une table qui résume les conditions et les actions possibles pour un certain problème. Pour plus de détails, veuillez consulter le site "Explication de la table de décision", qui est facile à comprendre. Le tableau de décision (calcul de la remise des frais de stationnement) et le chiffre suivant, qui font l'objet de ce temps, sont également cités à partir de ce site.

Partie description de la condition (talon de condition) Entrée de condition
photo_2.png photo_4.png
Partie de description de l'action (talon d'action) Entrée d'action
photo_3.png photo_5.png

Le contenu expliqué dans cet article est le suivant.

2. Mise en œuvre du tableau de décision

class-structure.jpg

Il est unique que chacune des conditions soit définie sur Règle, mais j'ai essayé de concevoir la classe pour qu'elle soit aussi cohérente que l'apparence de la table de décision. En conséquence, nous avons pu le réaliser avec 6 classes (3 interfaces et 1 classe abstraite). Comme vous pouvez le voir dans le contenu, il est implémenté uniquement avec des fonctions Java standard et sans branchement conditionnel.

2.1. DecisionAction

Il s'agit d'une interface pour définir des actions qui sont les résultats de jugement de la table de décision. Le contenu peut être librement défini en fonction de la table de décision que vous souhaitez réaliser.

DecisionAction.java


public interface DecisionAction {

}

2.2. RuleInput

Une interface pour définir les données d'entrée de Rule.

RuleInput.java


public interface RuleInput {

}

2.3. Rule

Une interface pour implémenter une logique de jugement conditionnel. ʻImplémenter le processus de jugement d'une condition dans la méthode Object evaluer (entrée RuleInput) `. La valeur de retour sera la valeur utilisée dans le jugement de la partie de spécification de condition. Tout type de données Java standard (valeur valide, type de chaîne, type numérique) fera l'affaire.

Rule.java


public interface Rule<I extends RuleInput> {

    Object evaluate(RuleInput input);

    @SuppressWarnings("unchecked")
    default I getInput(RuleInput input) {
        return (I) input;
    };
}

2.4. ConditionStub

Cette classe est un argument lors de l'exécution d'un jugement conditionnel. Comme vous pouvez le voir, il s'agit simplement d'un support pour la saisie de règles et de règles.

ConditionStub.java


public class ConditionStub {

    private Map<Rule<? extends RuleInput>, RuleInput> rules = new HashMap<>();

    public void when(Rule<? extends RuleInput> rule, RuleInput input) {
        rules.put(rule, input);
    }

    public Map<Rule<? extends RuleInput>, RuleInput> getRules() {
        return rules;
    }
}

2.5. ConditionEntry

Cette classe définit la partie description des conditions de la table de décision et l'opération déterminée à ce moment. En tant qu'élément de conservation des données, l'un des modèles de données définis dans les numéros 1 à 8 est conservé. Le but est d'utiliser le hashCode de Set comme id pour l'identifier de manière unique.

ConditionEntry.java


public class ConditionEntry<A extends DecisionAction> {

    private Set<String> resultMap = new HashSet<>();

    private A action;

    public void when(@SuppressWarnings("rawtypes") Class ruleClass, Object entry) {
        resultMap.add(ruleClass.getSimpleName() + "/" + entry);
    }

    public void then(A action) {
        this.action = action;
    }

    public int getId() {
        return resultMap.hashCode();
    }

    public A getAction() {
        return action;
    }
}

2.6. DecisionTable

Une classe qui définit une table de décision. En tant que données, il ne contient que ConditionEntry dans Map. S'il y a 8 modèles de données, # 1 à # 8, 8ConditionEntrys sont stockés dans Map. Lors de son utilisation, définissez les données de la table de décision avec ʻinitDecisionTable () `.

Le processus de détermination de l'action de la table de décision est la méthode «résoudre». Comme vous pouvez le voir dans le contenu, tout ce que vous avez à faire est d'exécuter la règle séquentiellement pour créer une instance de ConditionEntry et vérifier si l'id correspondant est enregistré dans la table de décision.

DecisionTable.java


public abstract class DecisionTable<A extends DecisionAction> {

    private Map<Integer, ConditionEntry<A>> decisionTable;

    public DecisionTable() {
        decisionTable = initDecisionTable();
    }

    protected abstract Map<Integer, ConditionEntry<A>> initDecisionTable();

    public A resolve(ConditionStub conditionStub) {
        // execute rule
        ConditionEntry<A> result = new ConditionEntry<>();
        conditionStub.getRules().forEach((R, I) -> {
            result.when(R.getClass(), R.evaluate(I));
        });
        // search action
        Integer key = result.getId();
        if (decisionTable.containsKey(key)) {
            return decisionTable.get(key).getAction();
        }
        return null;
    }
}

3. Mise en œuvre du tableau de décision de calcul des remises pour les frais de stationnement

photo.png

Implémentons le tableau de décision "Calcul de la remise des frais de stationnement". Il sera mis en œuvre tel qu'il en a vraiment l'air. Si vous souhaitez l'implémenter plus simplement, reportez-vous à "[4. Mise en œuvre du tableau de décision de calcul de la remise des frais de stationnement (version simple)](# 4-Mise en œuvre du tableau de décision de calcul de la remise des frais de stationnement, version simple)" S'il te plait donne moi.

3.1. Mise en œuvre de l'action décisionnelle

ParkingDiscountAction.java


public class ParkingDiscountAction implements DecisionAction {

    private final boolean discount30Minute;
    private final boolean discount1Hour;
    private final boolean discount2Hour30Minute;
    private final boolean discount3Hour30Minute;

    //Le constructeur est omis. Préparez un constructeur qui prend un champ comme argument.
    //getter est omis. Aucun setter n'est nécessaire car il ne contient que les résultats de la table de décision.
    //ToString pour vérifier le contenu()Il est pratique de générer automatiquement la méthode avec eclipse.
}

3.2. Mise en œuvre de RuleInput et Rule

PriceRuleInput.java


public class PriceRuleInput implements RuleInput {

    private final int paymentPrice;

    //Le constructeur est omis. Préparez un constructeur qui prend un champ comme argument.
    //getter est omis. La valeur d'entrée étant immuable, aucun setter n'est requis.
}

PriceRule1.java


public class PriceRule1 implements Rule<PriceRuleInput> {

    //Logique de traitement pour juger "3000 yens ou plus et moins de 10000 yens"
    @Override
    public Object evaluate(RuleInput input) {
        PriceRuleInput priceRuleInput = getInput(input);
        int paymentPrice = priceRuleInput.getPaymentPrice();
        if (paymentPrice >= 3000 && paymentPrice < 10000) {
            return true;
        }
        return false;
    }
}

PriceRule2.java


public class PriceRule2 implements Rule<PriceRuleInput> {

    //Logique de traitement pour juger "10 000 yens ou plus et moins de 30 000 yens"
    @Override
    public Object evaluate(RuleInput input) {
        PriceRuleInput priceRuleInput = getInput(input);
        int paymentPrice = priceRuleInput.getPaymentPrice();
        if (paymentPrice >= 10000 && paymentPrice < 30000) {
            return true;
        }
        return false;
    }
}

PriceRule3.java


public class PriceRule3 implements Rule<PriceRuleInput> {

    //Logique de traitement pour juger "30 000 yens ou plus"
    @Override
    public Object evaluate(RuleInput input) {
        PriceRuleInput priceRuleInput = getInput(input);
        int paymentPrice = priceRuleInput.getPaymentPrice();
        if (paymentPrice >= 30000) {
            return true;
        }
        return false;
    }
}

CinemaRuleInput.java


public class CinemaRuleInput implements RuleInput {

    private final boolean watchCinema;

    //Le constructeur est omis. Préparez un constructeur qui prend un champ comme argument.
    //getter est omis. La valeur d'entrée étant immuable, aucun setter n'est requis.
}

CinemaRule.java


public class CinemaRule implements Rule<CinemaRuleInput> {

    @Override
    public Object evaluate(RuleInput input) {
        CinemaRuleInput cinemaRuleInput = getInput(input);
        return cinemaRuleInput.isWatchCinema();
    }
}

3.3. Définition de la table de décision

class-structure.jpg

Définissez comme «ConditionEntry» dans le cadre rouge de la table de décision. La condition est ajoutée par la méthode when (), et l'opération à ce moment-là est définie par la méthode then (). Après avoir défini les huit modèles de données # 1 à # 8 dans la table de décision, ajoutez chacun à la carte. La clé à ce moment-là est l'id obtenu par getId () de ConditionEntry.

ParkingDiscountDecisionTable.java


public class ParkingDiscountDecisionTable extends DecisionTable<ParkingDiscountAction> {

    @Override
    protected Map<Integer, ConditionEntry<ParkingDiscountAction>> initDecisionTable() {
        // #1
        ConditionEntry<ParkingDiscountAction> pattern01 = new ConditionEntry<>();
        pattern01.when(PriceRule1.class, false);
        pattern01.when(PriceRule2.class, false);
        pattern01.when(PriceRule3.class, false);
        pattern01.when(CinemaRule.class, false);
        pattern01.then(new ParkingDiscountAction(true, false, false, false));
        // #2
        ConditionEntry<ParkingDiscountAction> pattern02 = new ConditionEntry<>();
        pattern02.when(PriceRule1.class, true);
        pattern02.when(PriceRule2.class, false);
        pattern02.when(PriceRule3.class, false);
        pattern02.when(CinemaRule.class, false);
        pattern02.then(new ParkingDiscountAction(false, true, false, false));
        // #3
        ConditionEntry<ParkingDiscountAction> pattern03 = new ConditionEntry<>();
        pattern03.when(PriceRule1.class, false);
        pattern03.when(PriceRule2.class, true);
        pattern03.when(PriceRule3.class, false);
        pattern03.when(CinemaRule.class, false);
        pattern03.then(new ParkingDiscountAction(false, false, true, false));
        // #4
        ConditionEntry<ParkingDiscountAction> pattern04 = new ConditionEntry<>();
        pattern04.when(PriceRule1.class, false);
        pattern04.when(PriceRule2.class, false);
        pattern04.when(PriceRule3.class, true);
        pattern04.when(CinemaRule.class, false);
        pattern04.then(new ParkingDiscountAction(false, false, false, true));
        // #5
        ConditionEntry<ParkingDiscountAction> pattern05 = new ConditionEntry<>();
        pattern05.when(PriceRule1.class, false);
        pattern05.when(PriceRule2.class, false);
        pattern05.when(PriceRule3.class, false);
        pattern05.when(CinemaRule.class, true);
        pattern05.then(new ParkingDiscountAction(false, false, true, false));
        // #6
        ConditionEntry<ParkingDiscountAction> pattern06 = new ConditionEntry<>();
        pattern06.when(PriceRule1.class, true);
        pattern06.when(PriceRule2.class, false);
        pattern06.when(PriceRule3.class, false);
        pattern06.when(CinemaRule.class, true);
        pattern06.then(new ParkingDiscountAction(false, false, true, false));
        // #7
        ConditionEntry<ParkingDiscountAction> pattern07 = new ConditionEntry<>();
        pattern07.when(PriceRule1.class, false);
        pattern07.when(PriceRule2.class, true);
        pattern07.when(PriceRule3.class, false);
        pattern07.when(CinemaRule.class, true);
        pattern07.then(new ParkingDiscountAction(false, false, true, false));
        // #8
        ConditionEntry<ParkingDiscountAction> pattern08 = new ConditionEntry<>();
        pattern08.when(PriceRule1.class, false);
        pattern08.when(PriceRule2.class, false);
        pattern08.when(PriceRule3.class, true);
        pattern08.when(CinemaRule.class, true);
        pattern08.then(new ParkingDiscountAction(false, false, false, true));
        // create map
        Map<Integer, ConditionEntry<ParkingDiscountAction>> tables = new HashMap<>();
        tables.put(pattern01.getId(), pattern01);
        tables.put(pattern02.getId(), pattern02);
        tables.put(pattern03.getId(), pattern03);
        tables.put(pattern04.getId(), pattern04);
        tables.put(pattern05.getId(), pattern05);
        tables.put(pattern06.getId(), pattern06);
        tables.put(pattern07.getId(), pattern07);
        tables.put(pattern08.getId(), pattern08);
        return tables;
    }
}

3.4. Utilisation de la table de décision

Utiliser la table de décision est facile, il suffit de créer une instance de «ConditionStub», puis de la passer comme argument à la méthode «resolver ()» de «ParkingDiscountDecisionTable» et de l'exécuter.

Il est recommandé de réutiliser l'instance ParkingDiscountDecisionTable au lieu de la créer à chaque fois, car la définition de la table de décision ne change pas pendant l'exécution.

ParkingDiscountService.java


public class ParkingDiscountService {

    //Injection si vous utilisez un conteneur DI
    PriceRule1 priceRule1;
    PriceRule2 priceRule2;
    PriceRule3 priceRule3;
    CinemaRule cinemaRule;
    ParkingDiscountDecisionTable parkingDiscountDecisionTable;
    
    public ParkingDiscountService() {
        //Instance au lieu d'injection
        priceRule1 = new PriceRule1();
        priceRule2 = new PriceRule2();
        priceRule3 = new PriceRule3();
        cinemaRule = new CinemaRule();
        parkingDiscountDecisionTable = new ParkingDiscountDecisionTable();
    }
    
    public void business(int paymentPrice, boolean watchCinema) {
        // create input data
        PriceRuleInput priceRuleInput = new PriceRuleInput(paymentPrice);
        CinemaRuleInput cinemaRuleInput = new CinemaRuleInput(watchCinema);
        // create conditionStub
        ConditionStub conditionStub = new ConditionStub();
        conditionStub.when(priceRule1, priceRuleInput);
        conditionStub.when(priceRule2, priceRuleInput);
        conditionStub.when(priceRule3, priceRuleInput);
        conditionStub.when(cinemaRule, cinemaRuleInput);
        // resolve by decisionTable
        ParkingDiscountAction parkingDiscountAction = parkingDiscountDecisionTable.resolve(conditionStub);
        System.out.println("paymentPrice : " + paymentPrice + ", watchCinema : " + watchCinema);
        System.out.println(parkingDiscountAction);
    }
}

3.5. Contrôle de fonctionnement

Demo.java


public class Demo {

    public static void main(String[] args) {
        //Si vous utilisez un conteneur DI, récupérez-le à partir de là
        //Cette fois, créez une instance sur place
        ParkingDiscountService service = new ParkingDiscountService();
        // # 1
        service.business(2000, false);
        // # 2
        service.business(5000, false);
        // # 3
        service.business(17000, false);
        // # 4
        service.business(45000, false);
        // # 5
        service.business(100, true);
        // # 6
        service.business(7000, true);
        // # 7
        service.business(20000, true);
        // # 8
        service.business(100000, true);
    }
}

Résultat d'exécution


paymentPrice : 2000, watchCinema : false
ParkingDiscountAction [discount30Minute=true, discount1Hour=false, discount2Hour30Minute=false, discount3Hour30Minute=false]
paymentPrice : 5000, watchCinema : false
ParkingDiscountAction [discount30Minute=false, discount1Hour=true, discount2Hour30Minute=false, discount3Hour30Minute=false]
paymentPrice : 17000, watchCinema : false
ParkingDiscountAction [discount30Minute=false, discount1Hour=false, discount2Hour30Minute=true, discount3Hour30Minute=false]
paymentPrice : 45000, watchCinema : false
ParkingDiscountAction [discount30Minute=false, discount1Hour=false, discount2Hour30Minute=false, discount3Hour30Minute=true]
paymentPrice : 100, watchCinema : true
ParkingDiscountAction [discount30Minute=false, discount1Hour=false, discount2Hour30Minute=true, discount3Hour30Minute=false]
paymentPrice : 7000, watchCinema : true
ParkingDiscountAction [discount30Minute=false, discount1Hour=false, discount2Hour30Minute=true, discount3Hour30Minute=false]
paymentPrice : 20000, watchCinema : true
ParkingDiscountAction [discount30Minute=false, discount1Hour=false, discount2Hour30Minute=true, discount3Hour30Minute=false]
paymentPrice : 100000, watchCinema : true
ParkingDiscountAction [discount30Minute=false, discount1Hour=false, discount2Hour30Minute=false, discount3Hour30Minute=true]

4. Mise en œuvre du tableau de décision pour le calcul de la remise des frais de stationnement (version simple)

compact-desition.jpg

Je ne sais pas si cela s'appelle une table de décision, mais les spécifications écrites sont les mêmes que la table de décision mentionnée ci-dessus. Si vous implémentez cela tel qu'il en a l'air, ce sera plus simple que le tableau de décision mentionné ci-dessus.

4.1. Mise en œuvre de l'action décisionnelle

Tant que l'interface DecisionAction est implémentée, il n'y a pas de limite au contenu, donc cette fois nous utiliserons un ENUM appelé Parking Discount.

ParkingDiscountAction.java


public class ParkingDiscountAction implements DecisionAction {

    private final ParkingDiscount discount;

    //Le constructeur est omis. Préparez un constructeur qui prend un champ comme argument.
    //getter est omis. Aucun setter n'est nécessaire car il ne contient que les résultats de la table de décision.
    //ToString pour vérifier le contenu()Il est pratique de générer automatiquement la méthode avec eclipse.
}

ParkingDiscount.java


public enum ParkingDiscount {

    THIRTY_MINUTE(1), 
    ONE_HOUR(2), 
    TWO_HOUR_THIRTY_MINUTE(3), 
    THREE_HOUR_THIRTY_MINUTE(4);

    private int code;

    //Le constructeur est omis. Préparez un constructeur qui prend un champ comme argument.
    //getter est omis.
}

4.2. Mise en œuvre de la règle

Implémentez PriceRule1, PriceRule2 et PriceRule3 mentionnés ci-dessus ensemble. La valeur de retour cette fois est un type numérique (int).

PriceRule.java


public class PriceRule implements Rule<PriceRuleInput> {

    @Override
    public Object evaluate(RuleInput input) {
        PriceRuleInput priceRuleInput = getInput(input);
        int paymentPrice = priceRuleInput.getPaymentPrice();
        if (paymentPrice >= 3000 && paymentPrice < 10000) {
            return 1;
        } else if (paymentPrice >= 10000 && paymentPrice < 30000) {
            return 2;
        } else if (paymentPrice >= 30000) {
            return 3;
        } else {
            return 0;
        }
    }
}

4.3. Définition de la table de décision

Étant donné que la valeur de retour de «PriceRule» est un type numérique (int), la valeur est également définie en conséquence dans la méthode «when». De même, la méthode then a le constructeur de ParkingDiscountAction changé en ENUM, définissez-le en conséquence.

ParkingDiscountDecisionTable.java


public class ParkingDiscountDecisionTable extends DecisionTable<ParkingDiscountAction> {

    @Override
    protected Map<Integer, ConditionEntry<ParkingDiscountAction>> initDecisionTable() {
        // #1
        ConditionEntry<ParkingDiscountAction> pattern01 = new ConditionEntry<>();
        pattern01.when(PriceRule.class, 0);
        pattern01.when(CinemaRule.class, false);
        pattern01.then(new ParkingDiscountAction(ParkingDiscount.THIRTY_MINUTE));
        // #2
        ConditionEntry<ParkingDiscountAction> pattern02 = new ConditionEntry<>();
        pattern02.when(PriceRule.class, 1);
        pattern02.when(CinemaRule.class, false);
        pattern02.then(new ParkingDiscountAction(ParkingDiscount.ONE_HOUR));
        // #3
        ConditionEntry<ParkingDiscountAction> pattern03 = new ConditionEntry<>();
        pattern03.when(PriceRule.class, 2);
        pattern03.when(CinemaRule.class, false);
        pattern03.then(new ParkingDiscountAction(ParkingDiscount.TWO_HOUR_THIRTY_MINUTE));
        // #4
        ConditionEntry<ParkingDiscountAction> pattern04 = new ConditionEntry<>();
        pattern04.when(PriceRule.class, 3);
        pattern04.when(CinemaRule.class, false);
        pattern04.then(new ParkingDiscountAction(ParkingDiscount.THREE_HOUR_THIRTY_MINUTE));
        // #5
        ConditionEntry<ParkingDiscountAction> pattern05 = new ConditionEntry<>();
        pattern05.when(PriceRule.class, 0);
        pattern05.when(CinemaRule.class, true);
        pattern05.then(new ParkingDiscountAction(ParkingDiscount.TWO_HOUR_THIRTY_MINUTE));
        // #6
        ConditionEntry<ParkingDiscountAction> pattern06 = new ConditionEntry<>();
        pattern06.when(PriceRule.class, 1);
        pattern06.when(CinemaRule.class, true);
        pattern06.then(new ParkingDiscountAction(ParkingDiscount.TWO_HOUR_THIRTY_MINUTE));
        // #7
        ConditionEntry<ParkingDiscountAction> pattern07 = new ConditionEntry<>();
        pattern07.when(PriceRule.class, 2);
        pattern07.when(CinemaRule.class, true);
        pattern07.then(new ParkingDiscountAction(ParkingDiscount.TWO_HOUR_THIRTY_MINUTE));
        // #8
        ConditionEntry<ParkingDiscountAction> pattern08 = new ConditionEntry<>();
        pattern08.when(PriceRule.class, 3);
        pattern08.when(CinemaRule.class, true);
        pattern08.then(new ParkingDiscountAction(ParkingDiscount.THREE_HOUR_THIRTY_MINUTE));
        // create map
        Map<Integer, ConditionEntry<ParkingDiscountAction>> tables = new HashMap<>();
        tables.put(pattern01.getId(), pattern01);
        tables.put(pattern02.getId(), pattern02);
        tables.put(pattern03.getId(), pattern03);
        tables.put(pattern04.getId(), pattern04);
        tables.put(pattern05.getId(), pattern05);
        tables.put(pattern06.getId(), pattern06);
        tables.put(pattern07.getId(), pattern07);
        tables.put(pattern08.getId(), pattern08);
        return tables;
    }
}

4.4. Utilisation de la table de décision

ParkingDiscountService.java


public class ParkingDiscountService {

    //Injection si vous utilisez un conteneur DI
    PriceRule priceRule;
    CinemaRule cinemaRule;
    ParkingDiscountDecisionTable parkingDiscountDecisionTable;

    public ParkingDiscountService() {
        //Instance au lieu d'injection
        priceRule = new PriceRule();
        cinemaRule = new CinemaRule();
        parkingDiscountDecisionTable = new ParkingDiscountDecisionTable();
    }

    public void business(int paymentPrice, boolean watchCinema) {
        // create input data
        PriceRuleInput priceRuleInput = new PriceRuleInput(paymentPrice);
        CinemaRuleInput cinemaRuleInput = new CinemaRuleInput(watchCinema);
        // create conditionStub
        ConditionStub conditionStub = new ConditionStub();
        conditionStub.when(priceRule, priceRuleInput);
        conditionStub.when(cinemaRule, cinemaRuleInput);
        // resolve by decisionTable
        ParkingDiscountAction parkingDiscountAction = parkingDiscountDecisionTable.resolve(conditionStub);
        System.out.println("paymentPrice : " + paymentPrice + ", watchCinema : " + watchCinema);
        System.out.println(parkingDiscountAction);
    }
}

4.5. Contrôle de fonctionnement

Résultat d'exécution


paymentPrice : 2000, watchCinema : false
ParkingDiscountAction [discount=THIRTY_MINUTE]
paymentPrice : 5000, watchCinema : false
ParkingDiscountAction [discount=ONE_HOUR]
paymentPrice : 17000, watchCinema : false
ParkingDiscountAction [discount=TWO_HOUR_THIRTY_MINUTE]
paymentPrice : 45000, watchCinema : false
ParkingDiscountAction [discount=THREE_HOUR_THIRTY_MINUTE]
paymentPrice : 100, watchCinema : true
ParkingDiscountAction [discount=TWO_HOUR_THIRTY_MINUTE]
paymentPrice : 7000, watchCinema : true
ParkingDiscountAction [discount=TWO_HOUR_THIRTY_MINUTE]
paymentPrice : 20000, watchCinema : true
ParkingDiscountAction [discount=TWO_HOUR_THIRTY_MINUTE]
paymentPrice : 100000, watchCinema : true
ParkingDiscountAction [discount=THREE_HOUR_THIRTY_MINUTE]

5. Enfin

Cette fois, j'ai expliqué comment implémenter la table de décision, qui est implémentée telle qu'elle en a l'air. En faisant bon usage des collections (Set, Map), nous avons pu l'implémenter sans utiliser de branchement conditionnel. Il semble difficile d'implémenter la classe DecisionTable qui définit le contenu de la table de décision, mais comme le contenu est standard, il semble préférable de le générer automatiquement à partir du document de conception dans un projet réel.

Recommended Posts

Réaliser une table de décision sans utiliser le branchement conditionnel
Comment joindre une table sans utiliser DBFlute et SQL
Branchement conditionnel avec une interface fluide
[Android] Créer un menu coulissant sans utiliser la vue de navigation
Lors de l'exécution d'une jointure externe complète sans utiliser de jointure externe complète
Trouvez le reste divisé par 3 sans utiliser de nombre