Hoe u veel if-verklaringen in Java kunt vervangen

1. Overzicht

Beslissingsconstructies zijn een essentieel onderdeel van elke programmeertaal. Maar we komen terecht bij het coderen van een groot aantal geneste if-statements die onze code complexer en moeilijker te onderhouden maken.

In deze tutorial zullen we de verschillende manieren om geneste if-instructies te vervangen.

Laten we verschillende opties bekijken om de code te vereenvoudigen.

2. Casestudy

Vaak komen we een bedrijfslogica tegen die veel voorwaarden met zich meebrengt, en elk ervan heeft een andere verwerking nodig. Laten we voor een demo het voorbeeld nemen van een Rekenmachine klasse. We zullen een methode hebben die twee getallen en een operator als invoer nodig heeft en het resultaat retourneert op basis van de bewerking:

openbare int berekenen (int a, int b, String-operator) {int resultaat = Geheel getal.MIN_VALUE; if ("add" .equals (operator)) {resultaat = a + b; } else if ("vermenigvuldigen" .equals (operator)) {resultaat = a * b; } else if ("delen" .equals (operator)) {resultaat = a / b; } else if ("aftrekken" .equals (operator)) {resultaat = a - b; } resultaat teruggeven; }

We kunnen dit ook implementeren met schakelaar verklaringen:

openbare int berekenUsingSwitch (int a, int b, String-operator) {switch (operator) {case "add": result = a + b; breken; // andere gevallen} resultaat retourneren; }

In typische ontwikkeling, de if-uitspraken kunnen veel groter en complexer van aard worden. Ook, de switch-statements passen niet goed als er complexe omstandigheden zijn.

Een ander neveneffect van geneste beslissingsconstructies is dat ze onhandelbaar worden. Als we bijvoorbeeld een nieuwe operator moeten toevoegen, moeten we een nieuwe if-instructie toevoegen en de bewerking implementeren.

3. Herstructurering

Laten we eens kijken naar de alternatieve opties om de complexe if-instructies hierboven te vervangen door veel eenvoudigere en beheersbare code.

3.1. Fabrieksklasse

Vaak komen we beslissingsconstructies tegen die uiteindelijk in elke branch dezelfde bewerking uitvoeren. Dit biedt de mogelijkheid om extraheer een fabrieksmethode die een object van een bepaald type retourneert en de bewerking uitvoert op basis van het concrete objectgedrag.

Laten we voor ons voorbeeld een Operatie interface die een enkele van toepassing zijn methode:

openbare interface Operatie {int toepassen (int a, int b); }

De methode gebruikt twee getallen als invoer en retourneert het resultaat. Laten we een klasse definiëren voor het uitvoeren van toevoegingen:

public class Addition implementeert Operatie {@Override public int apply (int a, int b) {return a + b; }}

We gaan nu een fabrieksklasse implementeren die exemplaren van Operatie gebaseerd op de gegeven operator:

openbare klasse OperatorFactory {statische kaart operationMap = nieuwe HashMap (); statische {operationMap.put ("add", new Addition ()); operationMap.put ("divide", new Division ()); // meer operators} public static Optioneel getOperation (String-operator) {return Optioneel.ofNullable (operationMap.get (operator)); }}

Nu, in de Rekenmachine klasse, kunnen we de fabriek opvragen om de relevante bewerking te krijgen en deze toepassen op de bronnummers:

openbaar int berekenUsingFactory (int a, int b, String-operator) {Operatie targetOperation = OperatorFactory .getOperation (operator) .orElseThrow (() -> nieuwe IllegalArgumentException ("Ongeldige operator")); retourneer targetOperation.apply (a, b); }

In dit voorbeeld hebben we gezien hoe de verantwoordelijkheid wordt gedelegeerd aan losjes gekoppelde objecten die worden bediend door een fabrieksklasse. Maar er kunnen kansen zijn dat de geneste als verklaringen eenvoudig worden verschoven naar de fabrieksklasse die ons doel verslaat.

Alternatief, we kunnen een opslagplaats van objecten onderhouden in een Kaart die kan worden opgevraagd voor een snelle zoekopdracht. Zoals we gezien hebben OperatorFactory # operationMap dient ons doel. We kunnen ook initialiseren Kaart tijdens runtime en configureer ze voor opzoeken.

3.2. Gebruik van Enums

Naast het gebruik van Kaart, we kunnen ook gebruiken Enum om bepaalde bedrijfslogica te benoemen. Daarna kunnen we ze gebruiken in de geneste als verklaringen of schakelkastverklaringen. Als alternatief kunnen we ze ook gebruiken als een fabriek van objecten en ze strategiseren om de gerelateerde bedrijfslogica uit te voeren.

Dat zou ook het aantal geneste if-statements verminderen en de verantwoordelijkheid aan het individu delegeren Enum waarden.

Laten we eens kijken hoe we dit kunnen bereiken. In eerste instantie moeten we onze Enum:

openbare opsomming Operator {ADD, MULTIPLY, SUBTRACT, DIVIDE}

Zoals we kunnen zien, zijn de waarden de labels van de verschillende operatoren die verder zullen worden gebruikt voor berekeningen. We hebben altijd een optie om de waarden als verschillende voorwaarden te gebruiken in geneste if-statements of switchgevallen, maar laten we een alternatieve manier ontwerpen om de logica te delegeren aan de Enum zelf.

We zullen methoden definiëren voor elk van de Enum waarden en voer de berekening uit. Bijvoorbeeld:

TOEVOEGEN {@Override public int apply (int a, int b) {return a + b; }}, // andere operatoren public abstract int zijn van toepassing (int a, int b);

En dan in de Rekenmachine class, kunnen we een methode definiëren om de bewerking uit te voeren:

openbare int berekenen (int a, int b, operator operator) {return operator.apply (a, b); }

Nu kunnen we de methode aanroepen door het omzetten van de Draad waarde aan de Operator door de Operator # valueOf () methode:

@Test openbare leegte whenCalculateUsingEnumOperator_thenReturnCorrectResult () {Rekenmachine calculator = nieuwe rekenmachine (); int resultaat = calculator.calculate (3, 4, Operator.valueOf ("ADD")); assertEquals (7, resultaat); }

3.3. Commando patroon

In de vorige discussie hebben we het gebruik van de fabrieksklasse gezien om de instantie van het juiste bedrijfsobject voor de gegeven operator te retourneren. Later wordt het bedrijfsobject gebruikt om de berekening uit te voeren in de Rekenmachine.

We kunnen ook ontwerpen a Rekenmachine # berekenen methode om een ​​commando te accepteren dat kan worden uitgevoerd op de ingangen. Dit is een andere manier om genest als verklaringen.

We zullen eerst onze definiëren Opdracht koppel:

openbare interface Commando {Geheel getal execute (); }

Laten we vervolgens een AddCommand:

public class AddCommand implementeert Command {// Instancevariabelen public AddCommand (int a, int b) {this.a = a; this.b = b; } @Override public Integer execute () {return a + b; }}

Laten we tot slot een nieuwe methode introduceren in het Rekenmachine die het Opdracht:

openbare int berekenen (opdrachtopdracht) {retouropdracht.execute (); }

Vervolgens kunnen we de berekening aanroepen door een instantiëren AddCommand en stuur het naar het Rekenmachine # berekenen methode:

@Test openbare leegte whenCalculateUsingCommand_thenReturnCorrectResult () {Rekenmachine calculator = nieuwe rekenmachine (); int resultaat = calculator.calculate (nieuwe AddCommand (3, 7)); assertEquals (10, resultaat); }

3.4. Regel Engine

Wanneer we uiteindelijk een groot aantal geneste if-instructies schrijven, geeft elk van de voorwaarden een bedrijfsregel weer die moet worden geëvalueerd om de juiste logica te kunnen verwerken. Een rule engine haalt zo'n complexiteit uit de hoofdcode. EEN RuleEngine evalueert de Reglement en geeft het resultaat terug op basis van de invoer.

Laten we een voorbeeld bekijken door een eenvoudig te ontwerpen RuleEngine welke een Uitdrukking door middel van een set van Reglement en retourneert het resultaat van de geselecteerde Regel. Eerst definiëren we een Regel koppel:

openbare interface Regel {boolean evalueren (expressie-expressie); Resultaat getResult (); }

Ten tweede, laten we een RuleEngine:

openbare klasse RuleEngine {privé statische lijstregels = nieuwe ArrayList (); statische {rules.add (nieuwe AddRule ()); } public Result process (Expression expression) {Rule rule = rules .stream () .filter (r -> r.evaluate (expression)) .findFirst () .orElseThrow (() -> new IllegalArgumentException ("Expressie komt niet overeen met Regel")); return rule.getResult (); }}

De RuleEngine accepteert een Uitdrukking object en retourneert het Resultaat. Nu, laten we de Uitdrukking klasse als een groep van twee Geheel getal objecten met de Operator die zal worden toegepast:

openbare klasse Expressie {privé Geheel getal x; privé Geheel getal y; particuliere exploitant; }

En laten we tot slot een gewoonte definiëren AddRule klasse die alleen evalueert wanneer de Bewerking TOEVOEGEN is gespecificeerd:

public class AddRule implementeert Regel {@Override public boolean evalu (Expression expression) {boolean evalResult = false; if (expression.getOperator () == Operator.ADD) {this.result = expression.getX () + expression.getY (); evalResult = true; } terug evalResult; }}

We zullen nu de RuleEngine Met een Uitdrukking:

@Test openbare leegte whenNumbersGivenToRuleEngine_thenReturnCorrectResult () {Expressie-expressie = nieuwe expressie (5, 5, Operator.ADD); RuleEngine engine = nieuwe RuleEngine (); Resultaat resultaat = engine.process (uitdrukking); assertNotNull (resultaat); assertEquals (10, result.getValue ()); }

4. Conclusie

In deze tutorial hebben we een aantal verschillende opties onderzocht om complexe code te vereenvoudigen. We hebben ook geleerd hoe geneste if-statements kunnen worden vervangen door effectieve ontwerppatronen.

Zoals altijd kunnen we de volledige broncode vinden via de GitHub-repository.