CHANGING BEHAVIOR BASED ON THE VALUE OF AN ENUMERATED TYPE(ZT)

本文介绍了枚举类型的使用,不仅可替代常量,还能基于其值改变类的行为。展示了如何优雅实现枚举常量特定行为,如将switch语句移至枚举类。还提到枚举可含构造函数,以及通过访客模式根据枚举类型在客户端实现不同行为。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

When you first use enumerated types, you might simply see them as an alternative to using constants. However they have other interesting uses. For example, you can change the behavior of a class based on the value of an enumerated type. This tip will show you how to implement enumeration constant-specific behavior in an elegant way.

Begin by creating a simple enum type that represents the four basic types of U.S. coins:

   public enum Coin1 {
     PENNY,
     NICKEL,
     DIME,
     QUARTER;
   }

You can now switch on the value of an enum. For example, you can create a method getValue() that accepts an object of type Coin1:

   static int getValue(Coin1 coin) {
     switch( coin ) {
       case PENNY: //do something
       // .. remaining cases
     }
   }

You can also iterate through the different values in Coin1 using the enhanced for:

    for (Coin1 coin: Coin1.values())

Let's put those two techniques together into a client for Coin1:

   public class Dispenser1 {

     static int getValue(Coin1 coin){
       switch(coin) {
         case PENNY:return 1;
         case NICKEL:return 5;
         case DIME: return 10;
         case QUARTER: return 25;
         default: return 0;
       }
     }
     public static void main(String[] args) {
       for (Coin1 coin: Coin1.values())  {
         System.out.println("A " + coin + " has value = "
         + getValue(coin) + ".");
       }
     }
   } 

In this example, the default method returns a value of 0. In production code, you might prefer to have the default case throw an exception.

The value of coin.toString() is the name of the enum instance, so the output will look like this:

 A PENNY has value = 1.
 A NICKEL has value = 5.
 A DIME has value = 10.
 A QUARTER has value = 25.

In a straightforward example like the previous one, the switch statement can easily be moved from the client class to the enumeration class, so that the switch information is part of the Coin enum's functionality. Later, you will see an example where you need the client to switch on the enumeration type and decide what to do itself. In Coin1, each type has a single behavior, so let's move the switch statement to the enum. This is shown in the enum Coin2, where the switch statement is in the value() method of the enum class.

Newcomers to enumerated types think of them as representing constants, and forget that you can add methods to the definition. Remember that enums have the flexibility of classes and so you have many choices regarding how or when to implement different functionality. The value() method belongs to the type and is available to each instance.

   public enum Coin2 {
     PENNY,
     NICKEL,
     DIME,
     QUARTER;

     int value(){
        switch(this) {
         case PENNY:return 1;
         case NICKEL:return 5;
         case DIME: return 10;
         case QUARTER: return 25;
         default: return 0;
       }
     }
   }

Now the client code can be simplified:

   public class Dispenser2 {

     public static void main(String[] args) {
        for (Coin2 coin: Coin2.values())  {
         System.out.println("A " + coin + " has value = "
         + coin.value() + ".");
       }
     }
   }

You can eliminate the switch statement entirely by specifying different behavior within constant-specific methods. Make value() abstract so that it can be called by client code. Defining the method in the enum itself guarantees that it can be called on any constant. Declaring the method abstract ensures each constant will override the implementation.

   public enum Coin3 {
     PENNY { int value(){ return 1;}},
     NICKEL { int value(){ return 5;}},
     DIME { int value() { return 10;}},
     QUARTER { int value() {return 25;}};

     abstract int value();
   }

You can call Coin3 by changing the appropriate line of Dispenser2 to:

   for (Coin3 coin: Coin3.values())

Enumerated types can also contain constructors. You can supply Coin3 with a field named coinValue that holds an int and is set by a constructor:

   private Coin3( int value) {
     coinValue = value;
   }

The private keyword is superfluous here because enum constructors are implicitly private. It is included here as a visual reminder that the constructor cannot be explicitly called (it is dropped in the code listing below).

Now the value() method just returns coinValue. The other change is that each class must set coinValue. You do this by changing the list from:

   PENNY, NICKEL, DIME, QUARTER;

to:

   PENNY(1), NICKEL(5), DIME(10), QUARTER(25);

Here is the modified version of Coin3:

   public enum Coin3 {
     PENNY(1),
     NICKEL(5),
     DIME(10),
     QUARTER(25);

     private int coinValue;

     int value() {return coinValue;}

     Coin3(int value){
       coinValue = value;
     }
   }

Let's return to the case where you might want different behavior in the client depending on the enumeration type. For example, if you had an Employee enum, you might need to vary behavior in different parts of a human resources application depending on whether the Employee type was SALARY, HOURLY, or HOURLY_PART_TIME. This difference might be applicable in the calculation of days off, bonuses, benefits, and pay. You don't want to implement a switch statement in each client that calls different methods based on the type that is called. Ideally, you would like the correct method to be called automatically based on the type.

One way to accomplish this is by implementing the Visitor pattern. A classic reference is Design Patterns, Elements of Reusable Object-Oriented Software by Gamma, Helm, Johnson, and Vlissides. There are variants on the Visitor pattern -- only one version is discussed in this tip. The point of using it here is that if you use the Visitor pattern with enums, any enum constant-specific function (and any number of these functions) can be implemented by using a visitor.

If you have never seen the Visitor pattern before, you might find it a bit confusing. An instance of the enumerated type is created, and a method is called -- in this case, accept(). The class containing the behavior is passed in as an argument. The enum instance then calls the appropriate method in the class containing the behavior. In this way, different behavior can be injected into a client class.

The first component is the enumerated type. This time accept() is an abstract method that is implemented in each specialized enum constant. For example, in PENNY, accept() is implemented as follows:

   void accept( CoinVisitor cv) {cv.visitPenny(this);

In other words, this instance of accept calls the visitPenny() method in the CoinVisitor instance that was passed in as a parameter. Here is the revised enum class:

   public enum Coin4 {
     PENNY {
       void accept( CoinVisitor cv) {cv.visitPenny(this);}
     },
     NICKEL {
       void accept( CoinVisitor cv) {cv.visitNickel(this);}
     },
     DIME {
       void accept( CoinVisitor cv) {cv.visitDime(this);}
     },
     QUARTER {
       void accept( CoinVisitor cv) {cv.visitQuarter(this);}
     };
     abstract void accept(CoinVisitor cv);
   }

In this case, there is only a single implementation of CoinVisitor so you could create a concrete class. But the point of this example is to show how you can set up the infrastructure to vary the behavior in a client class. So you will need the following abstract class:

   public abstract class CoinVisitor {
     void visitPenny(Coin4 c){}
     void visitNickel(Coin4 c){}
     void visitDime(Coin4 c){}
     void visitQuarter(Coin4 c){}
   }

Now any time you want to specify a particular behavior that varies for each of the Coin4 types, you extend CoinVisitor. For example, CoinValueVisitor prints out the name and value of the appropriate coin.

   public class CoinValueVisitor extends CoinVisitor {
     void visitPenny(Coin4 c){
       System.out.println("A penny has value = 1.");
     }
     void visitNickel(Coin4 c){
       System.out.println("A nickel has value = 5.");
     }
     void visitDime(Coin4 c) {
       System.out.println("A dime has value = 10.");
     }
     void visitQuarter(Coin4 c) {
       System.out.println("A quarter has value = 25.");
     }
   }

The client code still iterates through the values of the enum, but this time, if coin is an instance of Coin4 and cvv is an instance of the CoinValueVisitor, you initiate the correct behavior with the following call:

   coin.accept(cvv);

If coin is an instance of PENNY, then this call has the effect of calling the accept() method in PENNY, and passing in cvv. Then the visitPenny() method is called on cvv. This results in the printing of "A penny has value = 1." to standard out. Here is the revised class.

   public class Dispenser4 {
      public static void main(String[] args) {
        CoinValueVisitor cvv = new CoinValueVisitor();
       for (Coin4 coin: Coin4.values())  {
         coin.accept(cvv);
       }
     }
   }

The result is the following output:

   A penny has value = 1.
   A nickel has value = 5.
   A dime has value = 10.
   A quarter has value = 25.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值