ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 02.01.2026

Просмотров: 3462

Скачиваний: 0

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

Section 20.10

Chapter 20 · Abstract Members

473

The US object could define the quantities Cent, Dollar, and CurrencyUnit as shown in Listing 20.11. This definition is just like the previous definition of the US object, except that it adds three new fields. The field Cent represents an amount of 1 US.Currency. It’s an object analogous to a one-cent coin. The field Dollar represents an amount of 100 US.Currency. So the US object now defines the name Dollar in two ways. The type Dollar (defined by the abstract inner class named Dollar) represents the generic name of the Currency valid in the US currency zone. By contrast, the value Dollar (referenced from the val field named Dollar) represents a single US dollar, analogous to a one-dollar bill. The third field definition of CurrencyUnit specifies that the standard currency unit in the US zone is the Dollar (i.e., the value Dollar, referenced from the field, not the type Dollar).

object US extends CurrencyZone {

abstract class Dollar extends AbstractCurrency { def designation = "USD"

}

type Currency = Dollar

def make(cents: Long) = new Dollar { val amount = cents

}

val Cent = make(1) val Dollar = make(100)

val CurrencyUnit = Dollar

}

Listing 20.11 · The US currency zone.

The toString method in class Currency also needs to be adapted to take subunits into account. For instance, the sum of ten dollars and twenty three cents should print as a decimal number: 10.23 USD. To achieve this, you could implement Currency’s toString method as follows:

override def toString =

((amount.toDouble / CurrencyUnit.amount.toDouble) formatted ("%."+ decimals(CurrencyUnit.amount) +"f") +" "+ designation)

Here, formatted is a method that Scala makes available on several classes,

Cover · Overview · Contents · Discuss · Suggest · Glossary · Index


Section 20.10

Chapter 20 · Abstract Members

474

including Double.2 The formatted method returns the string that results from formatting the original string on which formatted was invoked according to a format string passed as the formatted method’s right-hand operand. The syntax of format strings passed to formatted is the same as that of Java’s String.format method. For instance, the format string %.2f formats a number with two decimal digits. The format string used in the toString shown previously is assembled by calling the decimals method on CurrencyUnit.amount. This method returns the number of decimal digits of a decimal power minus one. For instance, decimals(10) is 1, decimals(100) is 2, and so on. The decimals method is implemented by a simple recursion:

private def decimals(n: Long): Int =

if (n == 1) 0 else 1 + decimals(n / 10)

Listing 20.12 shows some other currency zones. As another refinement you can add a currency conversion feature to the model. As a first step, you could write a Converter object that contains applicable exchange rates between currencies, as shown in Listing 20.13. Then, you could add a conversion method, from, to class Currency, which converts from a given source currency into the current Currency object:

def from(other: CurrencyZone#AbstractCurrency): Currency = make(math.round(

other.amount.toDouble * Converter.exchangeRate (other.designation)(this.designation)))

The from method takes an arbitrary currency as argument. This is expressed by its formal parameter type, CurrencyZone#AbstractCurrency, which indicates that the argument passed as other must be an AbstractCurrency type in some arbitrary and unknown CurrencyZone. It produces its result by multiplying the amount of the other currency with the exchange rate between the other and the current currency.3

The final version of the CurrencyZone class is shown in Listing 20.14. You can test the class in the Scala command shell. We’ll assume that the

2Scala uses rich wrappers, described in Section 5.9, to make formatted available.

3By the way, in case you think you’re getting a bad deal on Japanese yen, the exchange rates convert currencies based on their CurrencyZone amounts. Thus, 1.211 is the exchange rate between US cents to Japanese yen.

Cover · Overview · Contents · Discuss · Suggest · Glossary · Index


Section 20.10

Chapter 20 · Abstract Members

475

object Europe extends

CurrencyZone {

abstract class Euro

extends AbstractCurrency {

def designation =

"EUR"

}

type Currency = Euro

def make(cents: Long) = new Euro { val amount = cents

}

val Cent = make(1) val Euro = make(100)

val CurrencyUnit = Euro

}

object Japan extends CurrencyZone {

abstract class Yen extends AbstractCurrency { def designation = "JPY"

}

type Currency = Yen

def make(yen: Long) = new Yen { val amount = yen

}

val Yen = make(1)

val CurrencyUnit = Yen

}

Listing 20.12 · Currency zones for Europe and Japan.

CurrencyZone class and all concrete CurrencyZone objects are defined in a package org.stairwaybook.currencies. The first step is to import everything in this package into the command shell:

scala> import org.stairwaybook.currencies._

You can then do some currency conversions:

scala> Japan.Yen from US.Dollar * 100 res16: Japan.Currency = 12110 JPY

scala> Europe.Euro from res16 res17: Europe.Currency = 75.95 EUR

Cover · Overview · Contents · Discuss · Suggest · Glossary · Index


Section 20.10

Chapter 20 · Abstract Members

476

object Converter {

 

 

 

 

var exchangeRate = Map(

 

 

 

 

"USD" -> Map("USD" ->

1.0

, "EUR" ->

0.7596,

"JPY" ->

1.211

, "CHF" ->

1.223),

"EUR" -> Map("USD" ->

1.316

, "EUR" ->

1.0

,

"JPY" ->

1.594

, "CHF" ->

1.623),

"JPY" -> Map("USD" ->

0.8257, "EUR" ->

0.6272,

"JPY" ->

1.0

, "CHF" ->

1.018),

"CHF" -> Map("USD" ->

0.8108, "EUR" ->

0.6160,

"JPY" ->

0.982

, "CHF" ->

1.0

)

)

 

 

 

 

}

 

 

 

 

Listing 20.13 · A converter object with an exchange rates map.

scala> US.Dollar from res17 res18: US.Currency = 99.95 USD

The fact that we obtain almost the same amount after three conversions implies that these are some pretty good exchange rates!

You can also add up values of the same currency:

scala> US.Dollar * 100 + res18 res19: US.Currency = 199.95 USD

On the other hand, you cannot add amounts of different currencies:

scala> US.Dollar + Europe.Euro <console>:10: error: type mismatch;

found : Europe.Euro required: US.Currency

US.Dollar + Europe.Euro

ˆ

By preventing the addition of two values with different units (in this case, currencies), the type abstraction has done its job. It prevents us from performing calculations that are unsound. Failures to convert correctly between different units may seem like trivial bugs, but they have caused many serious systems faults. An example is the crash of the Mars Climate Orbiter

Cover · Overview · Contents · Discuss · Suggest · Glossary · Index


Section 20.10

Chapter 20 · Abstract Members

477

abstract class CurrencyZone {

type Currency <: AbstractCurrency def make(x: Long): Currency

abstract class AbstractCurrency {

val amount: Long

def designation: String

def + (that: Currency): Currency = make(this.amount + that.amount)

def * (x: Double): Currency = make((this.amount * x).toLong)

def - (that: Currency): Currency = make(this.amount - that.amount)

def / (that: Double) = make((this.amount / that).toLong)

def / (that: Currency) = this.amount.toDouble / that.amount

def from(other: CurrencyZone#AbstractCurrency): Currency = make(math.round(

other.amount.toDouble * Converter.exchangeRate (other.designation)(this.designation)))

private def decimals(n: Long): Int =

if (n == 1) 0 else 1 + decimals(n / 10)

override def toString =

((amount.toDouble / CurrencyUnit.amount.toDouble) formatted ("%."+ decimals(CurrencyUnit.amount) +"f") +" "+ designation)

}

val CurrencyUnit: Currency

}

Listing 20.14 · The full code of class CurrencyZone.

Cover · Overview · Contents · Discuss · Suggest · Glossary · Index