您的位置:首页 > Web前端

Java Tip 27: Typesafe constants in C++ and Java

2013-05-22 21:01 591 查看
This article offers an alternative to using
enum
s or
const int
s
to help eliminate range checking and runtime errors -- first looking at C++ and then exploring an implementation of the same using Java. The summary above shows one of the problems associated with using these constructs. Another problem is the additional range
checking that is required to validate the parameter (see below).

Probably the best known and most widely used approach in C++ is through the use of enum.

class Car

{
public:

enum{FORD,VW,SAAB,MAX_CAR};
};
void car_wash(int car)

{

//requires range checking on Cars

if(car>=0 && car<MAX_CAR)

{

//ok

}

}
void main(void)

{

car_wash(Car::FORD);

}


The main problem with the approach above is that any
int
value or object with a conversion to
int
(that is, a class with
operator
int()
) can be passed to
car_wash()
. For example, there is nothing stopping someone from using
car_wash()
like this:
car_wash(100);
--
or someone who knows that
Car::FORD
is zero from being lazy and calling
car_wash(0)
. This leaves the implementor of
car_wash()
with
the responsibility of range-checking the parameter.

The next example takes advantage of the C++ compiler's type checking, but it is still open to abuse through the use of a cast.

class Car

{
public:

enum Type{FORD,VW,SAAB};
};
void car_wash(Car::Type c)

{

//do something with c

}
void main(void)

{
Car::Type car=Car::FORD;

car_wash(car);  //OK car_wash is expecting a Car::Type parameter

car_wash(1); // error, no conversion from int Car::Type

}


Even though
Car::Type
is an
enum
and therefore ultimately is an
int
, the compiler
stops clients of
car_wash()
from passing an
int
as a parameter. However, although this approach is an improvement on the previous example,
it is still open to abuse by undermining the compiler's type checking through the use of a cast. For example, the compiler can not prevent someone from casting an
int
to a Car::Type.

void main(void)

{
Car::Type car=(Car::Type)100;

car_wash(car);

}


Now this leaves us back at square one. Again a cautious implementor of car_wash() will feel obliged to do some form of range checking.

NOTE: The
class
keyword can be replaced by
namespace
in these C++ examples if your compiler supports it and the
enum
s
could also be replaced by
const int
. But the problem we are trying to eliminate remains the same: the need for range checking and the potential for runtime errors.

Below I offer an alternative to the above methods that gives both type safety and "constness."

class Car

{
private:
//private ctor to stop instantiation of client Car objects

Car() {}

public:

static const Car FORD;

static const Car VW;

static const Car SAAB;
int operator==(const Car &car) const

{

return &car==this;

}
};
//static initialization
const Car Car::FORD;

const Car Car::VW;

const Car Car::SAAB;
void car_wash(const Car &car)

{
if(Car::FORD==car)

{

cout << "The Car being washed is a FORD" << endl;

}

else if(Car::VW==car)

{

cout << " The Car being washed is a VW" << endl;

}

//

}
void main(void)

{

car_wash(Car::VW);

}


Notice the use of the private ctor (constructor) this stops the instantiation of client
Car
objects. Because clients of class
Car
can not
instantiate their own objects, the need to perform any range checking in
car_wash()
is eliminated.

One last area of improvement would be to remove the nasty
if, else if
clauses from
car_wash()
. The presence of these clauses is a common
problem with enumerated types or constants. Although client code becomes more readable through the use of constant objects, the class methods that receive these objects as parameters can become giant switch statements or a series of
if,
else if
clauses.

In a future tip I will explore various techniques that can be used to eliminate these problems.

In the meantime I will leave it as an exercise for the reader to come up with an alternative design for
car_wash()
that removes the
if, else if
clauses.

Finally, for those of you who have been patient enough to wade through C++ desperate to get your hands on some Java (and who can blame you!), here is the Java implementation of the
Car
class. The
AWT (abstract windowing toolkit) uses a similar approach in some classes.

public final class Car

{

private Car() {}

public static final Car FORD=new Car();

public static final Car VW=new Car();

public static final Car SAAB=new Car();

}


Notice that in this case, as in many others, the Java implementation is more elegant than the C++ implementation. Also note the use of "final" in the class declaration. The "final" keyword declares that a class can not be subclassed.

At this point some of you may be thinking that the declaration of class
Car
as final may be too restrictive and that the class should be non-final with a protected ctor to allow subclasses
to add new cars. Although not an award-winning design, the Java code below illustrates how the subclassing of class
Car
can reintroduce the problems we have been trying to eliminate.

import java.util.Vector;
public class Car

{

protected Car() {}

public static final Car FORD=new Car();

public static final Car VW=new Car();

public static final Car SAAB=new Car();

}
public class FrenchCar extends Car

{

public static final Car CITROEN=new Car();

protected FrenchCar()

{

}

}
public class CarWash

{

public void Start(Car car)

{

//

}

}
public class SuperCarWash extends CarWash

{

public void Start(Car car)

{

if(FrenchCar.CITROEN==car)

{

//do some specialized washing

//could still call super.Start(car)

//to finish off

}

else

super.Start(car);

}

}
public class SuperCarWashOwner

{

private SuperCarWash superCarWash= new SuperCarWash();

private Vector carsToWash        = new Vector();
public void AddCar(Car c)

{

carsToWash.addElement(c);

}

public void WashCars()

{

//iterate cars an call superCarWash.Start

}

}
public class Test

{

private SuperCarWashOwner scwOwner = new SuperCarWashOwner();
public void TestMethod()

{

scwOwner.AddCar(Car.VW);

//add more cars

scwOwner.WashCars();

}

}


The first thing to note in the code above is that the potential exists for clients of
CarWash
to pass a
FrenchCar
to
CarWash.Start()
.
To improve matters,
CarWash.Start()
could throw an exception if a
FrenchCar
object is passed in, but all this leads us back to where we
started -- trying to design typesafe constants!

It is left as an exercise for the reader to explore this last technique and balance the advantages with the tradeoffs.

About the author

Philip Bishop is technical director and a consultant at Eveque Systems Ltd. in the UK. Eveque specializes in designing and implementing distributed object systems using Java, CORBA, C++, ODBMS, and so on. He can be reached at
phil@eveque.demon.co.uk.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: