Principles+3.pdf
Document Details
![StatelyAgate7771](https://quizgecko.com/images/avatars/avatar-17.webp)
Uploaded by StatelyAgate7771
2023
Tags
Full Transcript
Generic Classes • What if implementations of a class only differ by type? List MenuList • • Implementation based on type “Object” is questionable • • Frequent type conversions necessary Not type safe Overriding List::add to improve type safety? • • Violates principle of substitutability C...
Generic Classes • What if implementations of a class only differ by type? List MenuList • • Implementation based on type “Object” is questionable • • Frequent type conversions necessary Not type safe Overriding List::add to improve type safety? • • Violates principle of substitutability Covariance Dr. Bruno Schäffer © 2023 WindowList ButtonList List buttonList = new ArrayList(); //… buttonList.add(new Button()); buttonList.add(new Integer()); //… Button b1 = (Button)buttonList.get(0); Button b2 = (Button)buttonList.get(1); // -> run-time error @Override public void add(Object e) { if (!e instanceof Button) { throw new IllegalArgumentException(); } super.add(e); } Object-Oriented Software Development – Principles 55 Generic Classes • • • Generics allow to abstract over types Class specifies formal type parameters • • Actual type parameters are provided upon instantiation / inheritance Programming language feature Most (statically typed) programming languages provide generic classes: • • Eiffel, C++, C#, Swift, TypeScript, … In Java since Java 5 public class ArrayList<E> implements List<E> { public void add(E object) {}; public E get(int index) {}; } List<Button> buttonList = new ArrayList<Button>(); //… buttonList.add(new Button()); buttonList.add(new Integer()); // -> compile-time error //… Button b1 = buttonList.get(0); Integer b2 = buttonList.get(1); // -> compile-time error Dr. Bruno Schäffer © 2023 Object-Oriented Software Development – Principles 56 Generic Classes • • • • Java: generic classes are compatible with non-generic classes • • Example: Collection classes can be instantiated without actual type parameter “Object” is assumed upon missing type parameter Java base types (primitives) are not allowed for actual type parameters • Autoboxing / unboxing helps to avoid type conversions List<int> " Java reflection (meta information) was extended as well Java also provides generic methods public static <T> boolean contains(T[] array, T object) {...} • Java compilers applies type erase • • • Compiler generates byte code based on Object and type casts (where necessary) Backward compatibility leads to restrictions Type erasure ensures type safety without run-time impact Dr. Bruno Schäffer © 2023 Object-Oriented Software Development – Principles 57 Generic Classes • Advantages of generic classes • • Reliability (avoiding downcasts) Readability List l = new ArrayList(); //before Java 5 // vs. List<Button> l = new ArrayList<Button>(); //before Java 7 List<Button> l = new ArrayList<>(); //Java 7 • Autoboxing / -unboxing complements nicely // without autoboxing/-unboxing List<Integer> numbers = new ArrayList<Integer>(); numbers.add(new Integer(2)); int val = numbers.get(0).intValue(); // with autoboxing/-unboxing numbers.add(2); int val = numbers.get(1); • Disadvantage of generic classes • Language complexity (syntax and above all semantics) Dr. Bruno Schäffer © 2023 Object-Oriented Software Development – Principles 58 Inheritance and Generic Classes • Restricting genericity • Actual type parameter must conform to a specific type public class NumberList<T extends Number> { public void add(T object) {}; public T get(int index) {}; } NumberList<Double> numberList = new NumberList<>(); NumberList<String> stringList = new NumberList<>(); //compile-time error! • Combining inheritance and generics is desirable • • • Flexibility and reliability Even more complex syntax and semantics Is List<String> a subtype of List<Object>? No ➜ covariance Dr. Bruno Schäffer © 2023 Object-Oriented Software Development – Principles 59 Inheritance and Generic Classes • Design goals of a class API: • • Flexibility and type safety Readability • • Expressive and self documenting API Client should not care about typing challenges public static void printAll(List<Object> l) { for (Object o: l) { System.out.println(o); } } hardly usable List<String> stringList = new ArrayList()<>; printAll(stringList); //compile-time error! List<String> stringList = new ArrayList()<>; printAll(stringList); Dr. Bruno Schäffer © 2023 Object-Oriented Software Development – Principles 60 Inheritance and Generic Classes • Design goals of a class API: • • Flexibility and type safety Readability • • Expressive and self documenting API Client should not care about typing challenges public static void printAll(List<Object> l) { for (Object o: l) { System.out.println(o); } } hardly usable List<String> stringList = new ArrayList()<>; printAll(stringList); //compile-time error! public static void printAll(List<?> l) { for (Object o: l) { System.out.println(o); } } flexible List<String> stringList = new ArrayList()<>; printAll(stringList); Dr. Bruno Schäffer © 2023 Object-Oriented Software Development – Principles 60 Inheritance and Generic Classes • Case study: API design of a generic class “Stack” public class Stack<E> { public Stack(); public void push(E e); public E pop(); public boolean isEmpty(); } Dr. Bruno Schäffer © 2023 //Additional method for Stack (inflexible) public void pushAll(Iterable<E> src) { for (E e : src) push(e); } ! //Usage Stack<Number> numberStack = new Stack<Number>(); Iterable<Integer> integers = ...; numberStack.pushAll(integers); " //Additional method for Stack (flexible and safe) public void pushAll(Iterable<? extends E> src) { for (E e : src) push(e); } ! Object-Oriented Software Development – Principles 61 Inheritance and Generic Classes Dr. Bruno Schäffer © 2023 //Additional method for Stack (inflexible) public void popAll(Collection<E> dst) { while (!isEmpty()) dst.add(pop()); } ! //Usage Stack<Number> numberStack = new Stack<Number>(); Collection<Object> objects = ...; numberStack.popAll(objects); " //Additional method for Stack (flexible and safe) public void popAll(Collection<? super E> dst) { while (!isEmpty()) dst.add(pop()); } ! Object-Oriented Software Development – Principles 62 Inheritance and Generic Classes • When to use extends or super? • • PECS: producer extends, consumer super Stack example: src = producer (read access), dst = consumer (write access) Parameter produces T instances Parameter consumes T instances • • Yes No Yes Type<T> (Invariant in T) No Type<? super T> (Contravariant in T) Type<? extends T> (Covariant in T) Type<?> (Independent of T) ? extends T: upper-bounded wildcard, must be at least a T, cannot be more abstract ? super T: lower-bounded wildcard, must be at most a T, cannot be more specific Dr. Bruno Schäffer © 2023 Object-Oriented Software Development – Principles 63 Generic Classes – Takeaways Generic classes abstract from concrete types Genericity and inheritance: mind covariance PECS: producer extends, consumer super Dr. Bruno Schäffer © 2023 Object-Oriented Software Development – Principles 64 Meta-Information • • Information on properties of objects, classes, and the inheritance hierarchy Examples: | shape | Smalltalk shape respondsTo: #draw • def shape Groovy shape.metaClass.respondsTo(shape, “draw”) Used for: • • • Basic functionality, such as persisting objects Tools (e.g. debugger) Frameworks (e.g. type checking at run-time) • Java provides meta-information (RTTI) at run-time (via reflection API) • Meta classes • • • • Usually provided by pure oo languages A class is an object and hence instance of a class (= meta class) Meta classes provide all meta-information Meta-information can be modified Dr. Bruno Schäffer © 2023 Object-Oriented Software Development – Principles 65 Java Reflection • • Dynamic class loading Purpose: • • Minimizing dependencies Multiple implementations of a type (interface) are available but • • Only one implementation is needed at run-time or Only one implementation is executable in a specific run-time environment public class ReflectionExample { public static void main(String[] args) throws ... { String myTypeName = “FullyQualifiedNameOfMyType”; try { IMyType myInstance = (IMyType)Class.forName(myTypeName).newInstance(); } catch (Exception e) { } } } Dr. Bruno Schäffer © 2023 Object-Oriented Software Development – Principles 66 Java Reflection • • Class “Class” provides access to meta-information at run-time Example: calling a private method from outside its declaring class public class ReflectionExample { public static void main(String[] args) throws ... { List list = new ArrayList(); list.add(new Object()); Class clazz = list.getClass(); Method method = clazz.getDeclaredMethod("fastRemove", int.class); method.setAccessible(true); method.invoke(list, 0); } } • FEST-Reflect provides a much better (fluent) reflection API //… method("fastRemove").in(list).invoke(0) //… Dr. Bruno Schäffer © 2023 Object-Oriented Software Development – Principles 67 Factorization in Object-Oriented Systems Dr. Bruno Schäffer © 2023 Object-Oriented Software Development – Principles 68 Factorization in Object-Oriented Systems Factorization along the class hierarchy x super.x() Dr. Bruno Schäffer © 2023 Object-Oriented Software Development – Principles 68 Factorization in Object-Oriented Systems x Factorization along the class hierarchy super.x() Factorization outside of the class hierarchy Library A x Dr. Bruno Schäffer © 2023 a = new A(); a.x() Object-Oriented Software Development – Principles 68 Factorization in Object-Oriented Systems Factorization without explicit delegation Library B Y Dr. Bruno Schäffer © 2023 ? Object-Oriented Software Development – Principles 69 Aspect-Oriented Programming • Limitations of object-oriented programming • • • Factorization is only possible: • • along the class hierarchy by delegation Retrospective changes are hardly possible Cross-cutting concerns • Identical functionality spread across a software system • Example: database access • • Ramifications: Functionality: caching, authorization, transaction, logging, exception handling • • • • • Duplicated code Mixing domain logic and technical aspects Reduced maintainability: complex and replicated code Reusability questionable: technical aspects are platform dependent Aspect-Orientation promotes separation of (cross-cutting) concerns • Improves factorization of object-oriented design! Dr. Bruno Schäffer © 2023 Object-Oriented Software Development – Principles 70 Aspect-Oriented Programming • • • • Key unit of modularity OOP ➜ classes • • AOP ➜ aspects Cross-cutting concerns are factorized in aspects rather than classes Aspects allow for factorization outside of the type hierarchy Join-point model • Join Point • • Point in a running program where behavior can be added Examples: method call, access to instance variable etc. • Advice: • • Point Cut: Defines the behavior (before, after, and around) • • Quantify join points for executing an advice Point cuts can be defined via pattern matching Dr. Bruno Schäffer © 2023 Object-Oriented Software Development – Principles 71 AOP – Dynamic Proxies • • • Application of reflection Enables simple aspect-oriented programming Task: • • • Adding aspects to classes at run-time / changes to objects at run-time No source code changes to these classes No inheritance (base class cannot be changed in retrospect) client proxy target calling target method forwarding to target Dr. Bruno Schäffer © 2023 Object-Oriented Software Development – Principles 72 AOP – Dynamic Proxies • Class java.lang.reflect.Proxy • • Facilitates creating classes at run-time Classes may implement several interfaces public interface InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) … } public class Proxy implements java.io.Serializable { public static Class getProxyClass(ClassLoader loader, Class[] interfaces) … public static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) … public static boolean isProxyClass(Class cl) … public static InvocationHandler getInvocationHandler(Object proxy) … } Dr. Bruno Schäffer © 2023 Object-Oriented Software Development – Principles 73 AOP – Dynamic Proxies • Example: logging aspect • InvocationHandler logs before and after a method call public class LoggingInvocationHandler implements InvocationHandler { public static <T> T attachLogger(T obj, Logger logger) { return (T)Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new LoggingInvocationHandler(obj, logger)); } private Object target; private Logger logger; private LoggingInvocationHandler(Object target, Logger logger) { this.target = target; this.logger = logger; } … Dr. Bruno Schäffer © 2023 Object-Oriented Software Development – Principles 74 AOP – Dynamic Proxies … @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; logger.write(“Calling method “ + method.getName()); try { result = method.invoke(target, args); } catch (InvocationTargetException e) { logger.write(method.getName() + “ throws “ + e.getCause()); throw e.getCause(); } logger.write(“Returning from method “ + method.getName()); return result; } } Dr. Bruno Schäffer © 2023 Object-Oriented Software Development – Principles 75 AOP – Dynamic Proxies • Example of usage interface IShape { void draw(); } class Rectangle implements IShape { public void draw() { //… } } IShape shape = LoggingInvocationHandler.attachLogger(new Rectangle(), new Logger()); shape.draw(); • Bottom line • • • • No changes to IShape or its implementations required Creation of IShape instances must be adapted ( # factorize object creation) Usage of IShape instances remains unchanged (albeit slower) Application of decorator design pattern Dr. Bruno Schäffer © 2023 Object-Oriented Software Development – Principles 76 AOP – Google Guice • • • • • Google Guice* Lightweight dependency injection framework Dependency injection • Decoupling and configuration of dependencies Google Guice provides some infrastructure for AOP Approach • Advice • • Point cuts Interceptor implements advice • • • • • Annotations Matcher using pattern matching Module Definition of advices Objects must be created by injector Dr. Bruno Schäffer © 2023 *pronounced “juice” Object-Oriented Software Development – Principles 77 AOP – Google Guice – Example Adding Logging Aspect – Extending Concrete Classes public interface IShape { void draw(); } public Box implements IShape { public void draw() { //… } } Dr. Bruno Schäffer © 2023 public Circle implements IShape { public void draw() { //… } } Object-Oriented Software Development – Principles 78 AOP – Google Guice – Example Adding Logging Aspect – Extending Concrete Classes public interface IShape { void draw(); } public Box implements publicIShape Box implements { IShape { private Logger logger public= void new Logger(); draw() { //… public void draw() } { logger.write(“Start } method Box.draw"); //… logger.write(“End method Box.draw"); } } Dr. Bruno Schäffer © 2023 public Circle implements IShape { private public void Logger draw() logger { = new Logger(); //… public } void draw() { } logger.write(“Start method Circle.draw"); //… logger.write(“End method Circle.draw"); } } Object-Oriented Software Development – Principles 78 AOP – Google Guice – Example Adding Logging Aspect – Factorizing in Abstract Class public interface IShape { void draw(); } public abstract Shape implements IShape { private Logger logger = new Logger(); public void draw() { logger.write(“Start method Box.draw"); doDraw(); logger.write(“End method Box.draw"); } protected abstract void doDraw(); } public Box extends Shape { public Circle extends Shape { protected void doDraw() { //… protected void doDraw() { //… } } Dr. Bruno Schäffer © 2023 } } Object-Oriented Software Development – Principles 79 AOP – Google Guice – Example Adding Logging Aspect – Using GUICE Dr. Bruno Schäffer © 2023 Object-Oriented Software Development – Principles 80 AOP – Google Guice – Example Adding Logging Aspect – Using GUICE @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @interface Logging {} Dr. Bruno Schäffer © 2023 Object-Oriented Software Development – Principles 80 AOP – Google Guice – Example Adding Logging Aspect – Using GUICE @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @interface Logging {} public class LoggingInterceptor implements MethodInterceptor { private Logger logger = new Logger(); public Object invoke(MethodInvocation invocation) throws Throwable { logger.write(“Start method " + invocation.getMethod().getName()); Object object = invocation.proceed(); logger.write("End method " + invocation.getMethod().getName()); return object; } } Dr. Bruno Schäffer © 2023 Object-Oriented Software Development – Principles 80 AOP – Google Guice – Example Adding Logging Aspect – Using GUICE @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @interface Logging {} public class LoggingInterceptor implements MethodInterceptor { private Logger logger = new Logger(); public Object invoke(MethodInvocation invocation) throws Throwable { logger.write(“Start method " + invocation.getMethod().getName()); Object object = invocation.proceed(); logger.write("End method " + invocation.getMethod().getName()); return object; } } public interface IShape { @Logging void draw(); } Dr. Bruno Schäffer © 2023 Object-Oriented Software Development – Principles 80 AOP – Google Guice Adding Logging Aspect – Using GUICE public class Main extends AbstractModule { protected void configure() { bindInterceptor(Matchers.any(), Matchers.annotatedWith(Logging.class), new LoggingInterceptor()); } //what classes? //which methods? //interceptor to be called public static void main(String[] args) { Injector injector = Guice.createInjector(new Main()); IShape shape = injector.getInstance(Rectangle.class); shape.draw(); } } Dr. Bruno Schäffer © 2023 Object-Oriented Software Development – Principles 81 AOP – Google Guice • • Internals • • Google Guice dynamically creates subclasses Subclass overrides method and invokes interceptor Appraisal • Google Guice offers simple AOP for common use cases • • • Lightweight solution Simpler albeit not as powerful as comprehensive AOP approaches, e.g. Spring Way more usable than dynamic proxies (and more abstract, too) • Exemplary API • • Creating objects via injector can be cumbersome Flexible and expressive • • • Viral impact (all object instantiations have to be changed) Adding AOP retrospectively is rather difficult (even if not using annotations for point cuts) - Not all object instantiations may be accessible for modification Debugging • Noisy run-time stack Dr. Bruno Schäffer © 2023 Object-Oriented Software Development – Principles 82 Spring AOP – Exception Handling Factorizing exception handling in a minimally invasive manner with AOP //Exception handling not factorized @RestController @RequestMapping(path = "/", produces = MediaType.APPLICATION_JSON_VALUE) public class StudentController { Logger logger = LoggerFactory.getLogger(StudentController.class); @GetMapping(“/student/{id}") public ResponseEntity getStudent(@PathVariable long id) { try { return ResponseEntity.ok(retrieveStudent(id)); } catch (ResourceNotFoundException e) { logger.error("Resource not found", e); return ResponseEntity.notFound().build(); } } private Student retrieveStudent(long id) { if (id <= 99) { return new Student(id); } else { throw new ResourceNotFoundException("Invalid id”); } } } Dr. Bruno Schäffer © 2023 Object-Oriented Software Development – Principles 83 Spring AOP – Exception Handling //Delegating exception handling to Spring @ResponseStatus(HttpStatus.NOT_FOUND) public class ResourceNotFoundException extends RuntimeException { public ResourceNotFoundException(String msg) { super(msg); } } Dr. Bruno Schäffer © 2023 Object-Oriented Software Development – Principles 84 Spring AOP – Exception Handling //Exception handling factorized on controller level @RestController @RequestMapping(path = "/", produces = MediaType.APPLICATION_JSON_VALUE) public class StudentController { Logger logger = LoggerFactory.getLogger(StudentController.class); @ExceptionHandler({ResourceNotFoundException.class}) public ResponseEntity<String> handleException() { logger.error("Resource not found", e); return new ResponseEntity("Resource not found", HttpStatus.NOT_FOUND); } @GetMapping("/student/{id}") public ResponseEntity getStudent(@PathVariable long id) { return ResponseEntity.ok(retrieveStudent(id)); } private Student retrieveStudent(long id) { //… } } Dr. Bruno Schäffer © 2023 Object-Oriented Software Development – Principles 85 Spring AOP – Exception Handling //Exception handling factorized on global level @RestController @RequestMapping(path = "/", produces = MediaType.APPLICATION_JSON_VALUE) public class StudentController { @GetMapping("/student/{id}") public ResponseEntity getStudent(@PathVariable long id) { return ResponseEntity.ok(retrieveStudent(id)); } private Student retrieveStudent(long id) { //… } } @ControllerAdvice public class RestExceptionHandler { Logger logger = LoggerFactory.getLogger(RestExceptionHandler); @ExceptionHandler({ResourceNotFoundException.class}) public ResponseEntity<String> handleResourceNotFoundException( ResourceNotFoundException ex) { logger.error("Resource not found", ex); return new ResponseEntity("Resource not found", HttpStatus.NOT_FOUND); } } Dr. Bruno Schäffer © 2023 Object-Oriented Software Development – Principles 86 Aspect-Oriented Programming – Takeaways Separate domain logic from technical aspects Cross-cutting concerns can be factorized into aspects AOP may improve readability and maintainability Dr. Bruno Schäffer © 2023 Object-Oriented Software Development – Principles 87 Polymorphism • Multiple implementations of a method • • All implementations have the same method name The receiver and parameter types may be different • Methods accept arguments of different type • Polymorphic methods are easier to use • Example: “+“ in Java • Alternative: unique operators or method names Types of polymorphism • • • • Parametric ➜ Generic classes or methods Inheritance ➜ Several implementations along the inheritance hierarchy Overloading ➜ Name space extension (name & parameter) Coercion ➜ Implicit type conversion Dr. Bruno Schäffer © 2023 Object-Oriented Software Development – Principles 88 Polymorphism - Java Examples • • • • Parametric polymorphism ➜ generic classes Inheritance ➜ chapter “Principles” and “Class Libraries” Coercion ➜ e.g. autoboxing/-unboxing Overloading ➜ methods are differentiated by nr and types of parameters public class Logger { public void write(Integer i) {...} public void write(String s) {...} } //... Logger logger = new Logger(); logger.write(256); logger.write(“some string”); Dr. Bruno Schäffer © 2023 Object-Oriented Software Development – Principles 89 Literature • • • • • • Concepts • B. Meyer: Object-Oriented Software Construction Reflection in Java • I. Forman, N. Forman: Java Reflection in Action Java • J. Bloch: Effective Java Values • D. Bäumer et al: Values in Object Systems Aspect-oriented programming • • • G. Kiczales et al: Aspect-Oriented Programming AspectJ: http://www.eclipse.org/aspectj/ Google Guice: http://code.google.com/p/google-guice/ Picture Credits Duck Typing: http://geek-and-poke.com • Dr. Bruno Schäffer © 2023 Object-Oriented Software Development – Principles 90 Appendix – API Design Possible type errors with generics misuse //Stack public void pushAll(List<E> src) { for (E e : src) push(e); //reading is no problem src.set(0, this.pop()); //writing might lead to a type error } //Usage ArrayList<String> stringList = Arrays.asList(new String[]{“string1”,“string2”}); Stack<Object> objStack = new Stack<>(); objStack.push(new Object()); objStack.pushAll(stringList); //Java does not allow this! stringList.forEach(string -> { System.out.println(string.length());}); //Stack public void popAll(List<E> dst) { push(dst.get(0)); while (!isEmpty()) dst.add(pop()); } //reading might lead to a type error //writing is no problem //Usage ArrayList<Object> objectList = Arrays.asList(new Object[]{new Object()}); Stack<String> stringStack = new Stack<>(); stringStack.push(“string1”); stringStack.popAll(objectList); //Java does not allow this! Dr. Bruno Schäffer © 2023 Object-Oriented Software Development – Principles 91 Appendix – API Design • Java compiler enforces type safety with wildcard generics • • E cannot be assigned to <? extends E> <? super E> cannot be assigned to E public static class MyList<E> { E elem; public void addAll(MyList<? extends E> list) { this.elem = list.get() list.add((E)new Object()); } public void getAll(MyList<? super E> list) { list.add(elem); elem = list.get(); } public E get() { return elem; } public void add(E elem) { this.elem = elem; } } Dr. Bruno Schäffer © 2023 Object-Oriented Software Development – Principles 92