• No results found

Advanced Java Programming. Dependencies Among Objects. How can we implement BookStore? Consider the below set of dependent objects

N/A
N/A
Protected

Academic year: 2021

Share "Advanced Java Programming. Dependencies Among Objects. How can we implement BookStore? Consider the below set of dependent objects"

Copied!
8
0
0

Loading.... (view fulltext now)

Full text

(1)

Advanced Java Programming

The Dependency Injection design pattern decouples dependent objects so that they may be configured and tested independently. Google Guice manages dependencies among objects and handles the complexity of wiring together complex object relationships.

Dependency Injection

• The Dependency Injection design pattern • Testing with mock objects using Mockito • Managing dependencies with Google Guice

Copyright ©2010-2021 by David M. Whitlock. Permission to make digital or hard copies of part or all of this work for personal or classroom use is granted without fee provided that copies are not made or distributed for profit or commercial advantage and that copies bear this notice and full citation on the first page. To copy otherwise, to republish, to post on servers, or to redistribute to lists, requires prior specific permission and/or fee. Request permission to publish from

[email protected]. Last updated March 13, 2021.

1

Dependencies Among Objects

Consider the below set of dependent objects

REST

purchase(List<Book>, CreditCard) : double refund(List<Book> CreditCard) : double

BookStore remove(Book) add(Book) inStock(Book) : int BookInventory directory : File fileName : String BookDatabase

debit(CreditCard, dollars : int, cents : int) credit(CreditCard, dolars : int, cents : int)

CreditCardService

serverHost : String serverPort : int

FirstBankOfPSU FirstBankOfPortlandStateServlet

A BookStore requires a BookInventory and a CreditCardServiceto get its work done

• We’ve already extracted interfaces to allow us to abstract out the behavior that we need

• We want to be able to replace the implementations of BookInventoryand CreditCardService without affecting the code in BookStore

2

How can we implement BookStore?

package edu.pdx.cs410J.di; import java.io.File; import java.util.List; public class BookStore {

private final BookInventory inventory; private final CreditCardService cardService; public BookStore() {

String tmpdir =

System.getProperty( "java.io.tmpdir" ); File directory = new File( tmpdir ); this.inventory =

new BookDatabase( directory, "books.txt" ); this.cardService =

new FirstBankOfPSU( "localhost", 8080 ); }

When the BookStore is created, it also creates its dependencies

• Dependencies are stored in final fields because they only need to be created once

How can we implement BookStore?

public double purchase( List<Book> books, CreditCard card) { double total = 0.0d;

for (Book book : books) { inventory.remove(book); total += book.getPrice(); } CreditTransactionCode code = cardService.debit(card, total); if (code == CreditTransactionCode.SUCCESS ) { return total; } else {

throw new CreditCardTransactionException(code); }

}

(2)

What is wrong with this solution?

Sure, this code works, but.. • You can’t test this code!

– Tests would have to execute against the actual

database (slow) and credit card service ($$$)

– Lots of data setup: create database file and credit

card account

• The BookStore has to know how to configure its dependencies

– Does the BookStore really care what port the

credit card service runs on?

• If another class wants to use the BookDatabase, it would have to create another instance

– Two BookDatabases can’t work with the same file

• If you want to switch out another implementation of the dependencies, you have to change the

BookStore

– Again, does the BookStore really care what kind

of BookInventory is uses?

5

“Don’t call us, we’ll call you”

The “Hollywood Principle” states that dependent code should require that its dependencies are provided to it • Practically speaking, it means pass dependencies

into the constructor

public BookStore( BookInventory inventory,

CreditCardService cardService ) { this.inventory = inventory;

this.cardService = cardService; }

Now the BookStore class doesn’t have to know about the concrete types of its dependencies

• Let the main program create and configure dependencies

public class BookStoreApp {

public static void main(String... args) {

String tmpdir = System.getProperty( "java.io.tmpdir" ); File directory = new File( tmpdir );

BookInventory inventory =

new BookDatabase( directory, "books.txt" ); CreditCardService cardService =

new FirstBankOfPSU( "localhost", 8080 ); BookStore store =

new BookStore(inventory, cardService);

6

Testing with Mock Objects

Now that a BookStore gets passed its dependencies, you can test the class without using the “production”

BookInventoryand CreditCardService

Instead of using real dependencies, test with “mock” objects that provide an implementation of the interface that is useful for testing

package edu.pdx.cs410J.di;

public abstract class MockObject { protected void shouldNotInvoke() {

throw new UnsupportedOperationException(

"Did not expect this method to be invoked"); }

}

package edu.pdx.cs410J.di;

public class MockBookInventory extends MockObject implements BookInventory {

public void remove( Book book ) { shouldNotInvoke();

} }

Testing with Mock Objects

Test that BookStore invokes the expected methods of its dependencies

• Override interface methods to keep track of how it was invoked

package edu.pdx.cs410J.di; import org.junit.Test;

import java.util.Collections;

import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; public class BookStoreTest {

@Test

public void testBookIsPurchased() { final Book[] removedBook = new Book[1];

BookInventory inventory = new MockBookInventory() { public void remove( Book book ) {

removedBook[0] = book; }

};

CreditCardService cardService = new MockCreditCardService() {

public CreditTransactionCode debit( CreditCard card, double amount ) { return CreditTransactionCode.SUCCESS;

(3)

Testing with Mock Objects

Once the mock dependencies are set up • Create the BookStore

• Invoke the method you want to test

• Verify that the dependency was invoked as you expected

BookStore store =

new BookStore(inventory, cardService); CreditCard card = new CreditCard( "123" ); final Book testBook =

new Book("title", "author", 1.00d); double total = store.purchase(

Collections.singletonList(testBook), card ); assertEquals( testBook.getPrice(), total, 0.0d ); final Book removed = removedBook[0];

assertNotNull( removed ); assertEquals(testBook.getTitle(), removed.getTitle()); assertEquals(testBook.getAuthor(), removed.getAuthor()); assertEquals(testBook.getPrice(), removed.getPrice(), 0.0d); } } 9

Mock Objects are Awkward

Mock Objects get the job done, but... • Lots of verbose boilerplate code

• When interface changes (new method, e.g.), mock has to change

• Subclassing mock objects is hokey

– Lots of anonymous inner classes

– Have to keep track of state that was passed to

dependency

There’s got to be a better way, right?

10

Mock Objects with Mockito

There are several testing libraries for Java that allow you to easily create mock objects

We’re going to learn about Mockito (http://mockito.org/) • A Java API that creates mock objects for interfaces

and classes

• Contains a domain specific language (DSL) for constructing mock objects

• You can specify what behavior you expect out of the mock objects

Add the dependency in your pom.xml <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>1.8.5</version> <scope>test</scope> </dependency>

Mock Objects with Mockito

@Test

public void testCreditCardIsCharged() { Book book = mock(Book.class);

double price = 1.00d;

when( book.getPrice() ).thenReturn( price ); BookInventory inventory = mock(BookInventory.class); CreditCardService cardService = mock(CreditCardService.class); when(cardService.debit(any( CreditCard.class ), anyDouble() )) .thenReturn( CreditTransactionCode.SUCCESS ); BookStore store =

new BookStore(inventory, cardService); CreditCard card = mock(CreditCard.class); double total = store.purchase(

Collections.singletonList(book), card ); assertEquals( book.getPrice(), total, 0.0d ); verify( cardService ).debit( card, price ); }

(4)

Creating mock objects

The most interesting methods are all in the org.mockito.Mockitoclass

The mock method creates a mock object for an interface or class

• static <T> T mock(Class<T> classToMock) • Unless instructed otherwise, invoking a method of

the mock object will noop or return null

• Mockito uses crazy class loading tricks to accomplish this

We mocked all of our objects in the previous example • No need to invoke constructors

• Only had to specify the state we needed

– The test doesn’t care about the title or author

of the Book

• We can add a method to an interface without having to update a mock implementation

13

Specifying behavior of mock objects

The when method specifies how a method of the mock object should behave

• static <T> OngoingStubbing<T> when(T methodCall)

• Because they cannot be overridden by the magical class loader, final methods cannot be sent to when The OngoingStubbing class specifies what the behavior of the mock method

• thenReturn(T value) will cause the mock method to return a given value

• thenThrow(Throwable t) will throw an exception when the mock method is invoked

• thenCallRealMethod() will invoked the real implementation of the mock object to be invoked In general, you want to chain the when and then calls together

when( book.getPrice() ).thenReturn( price );

14

Method Argument Matching

The same mock method can be configured with different argument values

when( cardService.debit(card, price) .thenReturn(SUCCESS))

when( cardService.debit(card, -1.0) .thenReturn(INVALID_AMOUNT))

Sometimes the behavior of the mock method doesn’t depend on the value of its arguments

• Always return SUCCESS no matter which card is passed in

Matchers, the superclass of Mockito, has methods for filtering which values apply to the when

• Basically, there are bunch of type safe any methods • any() object (or null) or any(Class<T>) object of a

given type

• anyInt(), anyDouble(), anyString(), etc.

• isNull, isNotNull, and same(T), matches(String regex)

Method Argument Matching

If at least one arguments to a mock method is one of the “matchers”, then all arguments must be matchers

• Use the eq() methods for matching on a single value when( cardService.debit( any(CreditCard.class),

eq(-1.0) ) .thenReturn(INVALID_AMOUNT);

The AdditionalMatchers class has even more matchers • Boolean operations on matchers: and, or, not, etc. • Comparison operations: gt, lt, geq

(5)

Verifying Mock Objects

After configuring your mock objects and sending them to the API you want to test, the mock objects are verified

• Mock objects keep a history of what methods were invoked with which arguments

• The verify method tests whether or not a mock method was invoked with the expected arguments To verify that cardService was invoked with the expected cardand price:

verify( cardService ).debit( card, price ); You can also use matchers with verify

verify(cardService).debit( same(card), eq(price) );

17

Managing Dependencies

As your application scales, wiring together dependencies can become very cumbersome

• Large initialization code that references tons of classes

• Difficult to change out old implementations for new implementations

• End up relying on design patterns to create instances

– Factory Pattern

∗ Create instances of a class using a static method

– Singleton Pattern

∗ Access the one-and-only instance of a class via a static method

• Lots of boilerplate code that is tricky to get right

– Concurrent access to singletons – Life cycle of pooled/cached objects

18

Google Guice

Google Guice (pronounced “juice”) is a framework for configuring and injecting dependencies into Java classes

• Leverages annotations to provide type-safety • Well-known objects are registered in one place (a

“module”)

• Objects that depend on well-known objects are created by Guice via reflection

– Dependencies are injected into constructors and

fields Maven dependency: <dependency> <groupId>com.google.code.guice</groupId> <artifactId>guice</artifactId> <version>2.0</version> </dependency>

Managing Dependencies without Guice

From BookStoreApp.java

public static void main(String... args) throws JAXBException, IOException { String tmpdir =

System.getProperty("java.io.tmpdir"); File directory = new File(tmpdir); BookInventory inventory = new BookDatabase(directory); addBooks(inventory); CreditCardService cardService = new FirstBankOfPSU("localhost", 8080); Logger logger = Logger.getLogger("edu.pdx.cs410J.Logger"); logger.setLevel(Level.INFO); CheckoutPanel panel = new CheckoutPanel(inventory,cardService,logger); BookStoreGUI gui = new BookStoreGUI(panel);

gui.pack();

gui.setVisible( true ); }

If we want to introduce another panel that is for inventory control, we’d have to pass the well-known

(6)

Requesting Objects from Guice

Annotating one of a class’s constructors with @Inject instructs Guice to invoke that constructor when an instance is requested

• Guice will provide the well-known instances of the constructors arguments*

@Inject

public CheckoutPanel(BookInventory inventory, CreditCardService cardService, Logger logger ) {

If Guice is asked to create an instance of CheckoutPanel, it will invoke this constructor and provide an instance of BookInventoryand CreditCardService

• Your code doesn’t need to create the dependencies any more!

@Injectcan also be applied to methods and non-final instance fields methods that are invoked by Guice

• @Inject methods are invoked by Guice after the instance is created

*Guice automatically injects a Logger whose name is the name of the

class into which it is injected. And you can’t change it.

21

Guice Modules

A Guice “module” tells the Guice infrastructure about well-known objects

• Your modules subclass

com.google.inject.AbstractModule

Objects are “bound” into Guice using the bind method • Linking a type to its implementation (like a factory

method)

bind(BookInventory.class).to(BookDatabase.class); Whenever an object depends on a BookInventory, Guice will provide it with an instance of BookDatabase By default, Guice will create a new instance of a bound class when it is requested

Singleton objects in Guice can be configured in two ways • Annotating the object’s class with @Singleton • Binding the type in the singleton scope bind(CreditCardService.class)

.to(FirstBankOfPSU.class) .in(Singleton.class);

22

Annotations for Binding

The @Named annotation can be used to name well-known objects

From FirstBankOfPSU.java @Inject

public FirstBankOfPSU(

@Named("ServerHost") String serverHost, @Named("ServerPort") int serverPort ) In your module, you can bind the values of the named objects: bind(String.class) .annotatedWith(Names.named("ServerHost")) .toInstance("localhost"); bind(Integer.class) .annotatedWith(Names.named("ServerPort")) .toInstance( 8080 );

When Guice instantiates FirstBankOfPSU it will provide the values of the server host and port.

Annotations for Binding

You can also create your own annotation types for well-known objects

DataDirectoryis an annotation for the directory in which application data is stored

package edu.pdx.cs410J.di; import com.google.inject.BindingAnnotation; import java.lang.annotation.*; @BindingAnnotation @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER, ElementType.FIELD}) @Documented

public @interface DataDirectory { }

@BindingAnnotationmeans that the annotation being defined can be used with Guice

(7)

Annotations for Binding

You can annotate constructor parameters @Inject

public BookDatabase(@DataDirectory File directory) And bind the value in your module:

String tmpdir =

System.getProperty("java.io.tmpdir"); File directory = new File(tmpdir); bind(File.class)

.annotatedWith(DataDirectory.class) .toInstance(directory);

25

Creating an Injector

A Guice module is used to create an Injector which provides access to well-known objects

public class GuicyBookStoreApp extends BookStoreApp {

public static void main(String... args) { Injector injector = Guice.createInjector(new BookStoreModule()); BookInventory inventory = injector.getInstance(BookInventory.class); addBooks(inventory); BookStoreGUI gui = injector.getInstance(BookStoreGUI.class); gui.pack(); gui.setVisible(true); } }

Guice takes care of creating and configuring all of the dependencies of BookStoreGUI and wiring them together Ultimately, your code is more decoupled and can be reconfigured more easily

26

Provider Methods

Sometimes, objects need more than a constructor call in order to be initialized

• Or the constructor needs dependencies that are not provided by Guice

A module method that is annotated with @Provides constructs a Guice-managed instance of its return type This is probably a better way to configure the

DataDirectory File: @Provides

@DataDirectory

protected File provideDataDirectory() { String tmpdir =

System.getProperty("java.io.tmpdir"); return new File(tmpdir);

}

It would probably be better to construct the FirstBankOfPSUusing a provider method, also.

• You wouldn’t need the @Named constructor parameters that reused anywhere

Provider classes

A Provider class is used to create very expensive objects or objects that you want lazily initialize public class EncyrptionKeyProvider

implements Provider<EncryptionKey> {

private final EncryptionAlgorithm algorithm; @Inject

public EncyrptionKeyProvider(EncryptionAlgorithm algorithm) { this.algorithm = alrogithm;

}

@Override

public EncyrptionKey get() {

// Perform expensive operation on demand return new EncryptionKey(algorithm); }

}

Bind with:

bind(EncyrptionKey.class)

(8)

Provider classes

Guice can inject a provider into an object @Singleton

public class ConnectionManager { @Inject

private Provider<EncryptionKey> keyProvider; public ConnectionManager() {

// Should inject Provider as parameter instead }

public Connection createConnection() { return new Connection(keyProvider.get()); }

}

Even though the ConnectionManager is a singleton, it can get multiple EncyrptionKeys by injecting the Provider*

*Okay, ConnectionManager is a factory and we could probably doing

this Guicier, but you get the point

29

Summary

Dependency Injection is the ultimate expression of “programming to the interface”

• The “Hollywood Principle” states that objects should receive their dependencies upon construction • When dependencies are decoupled, finer-grained

testing is possible

• Mockito provides facilities for mocking objects and specifying mocked behavior

• Google Guice manages dependencies by separating configuration code from application code

References

Related documents

The aim of this study was therefore to determine the antioxidant capacity of artichoke using DPPH radical capture assay, alone and in combination with the QUENCHER procedure, and

The contract considered in this paper is the wholesale price contract model, the wholesale price w is determined by the manufacturer and retailer jointly, and under

Second, to the extent that a direct analogy to “time, place, and manner” regulations is appropriate, most—I would say almost all— regulations of weapons are indeed such

Since the result of fantasy game depends on skill of participant and not sheer chance, and winning or losing of virtual team created by the participant is also independent of outcome

This article is ultimately interested in how preservice teachers can be supported, in teacher education, to develop the professional judgment they will need to teach

Exceptional Student Education Department / Fernandina Beach High School Marcie Barker, One-on-One ESE Paraprofessional, effective August 21, 2020. Exceptional Student

Abstract The Global Trigger Tool (GTT) developed by the Institute for Healthcare Improvement is a method for retrospective patient record review based on the use of