Martin Fowler introduces a refactoring example of a program that calculates performance bills for a theatrical company. The Java program consists of a single function that calculates the bill for an invoice given play data. Fowler notes some issues with the design if the program were larger, including lack of structure. He plans to refactor the program to improve its design and make it easier to understand and modify.
download for better quality - Learn how to use an Applicative Functor to handle multiple independent effectful values through the work of Sergei Winitzki, Runar Bjarnason, Paul Chiusano, Debasish Ghosh and Adelbert Chang
The document discusses design patterns including the Gang of Four book, UML class diagrams, and categories of design patterns such as creational, structural, and behavioral patterns. It provides definitions and examples of specific design patterns including abstract factory, factory method, observer, and bridge patterns. Key aspects like intent, participants, collaborations, and implementations are covered for some of the patterns.
The document discusses three design patterns: Singleton, Observer, and Factory. The Singleton pattern ensures that only one instance of a class can exist and provides a global access point. The Observer pattern defines a subscription mechanism so that multiple objects can be notified of changes to an object they are observing. The Factory pattern provides an interface for creating objects but leaves the concrete class unspecified. Real-world examples and implementations of each pattern are provided.
Creational patterns deal with object creation and aim to create objects in a suitable manner. There are three main creational patterns: the factory pattern, which provides a consistent interface for creating properly configured objects; the abstract factory pattern, which is a factory for factories and handles more complex situations; and the singleton pattern, which ensures a single instance of an object and consistency of data by restricting object instantiation.
The Proxy design pattern provides a surrogate or placeholder for another object to control access to it. A proxy can act as a placeholder for complex, expensive objects that should not be initialized until needed. This delays the creation of these objects until they are actually required. Proxies can also protect the real component from undue complexity or provide extra functionality. Some common uses of proxies include remote proxies for objects in a different address space, virtual proxies that create objects on demand, and protection proxies that control access to the original object.
Folding Unfolded - Polyglot FP for Fun and Profit - Haskell and Scala - Part ...Philip Schwarz
(download for best quality slides) Gain a deeper understanding of why right folds over very large and infinite lists are sometimes possible in Haskell.
See how lazy evaluation and function strictness affect left and right folds in Haskell.
Learn when an ordinary left fold results in a space leak and how to avoid it using a strict left fold.
This version eliminates some minor imperfections and corrects the following two errors:
slide 15: "as sharing is required" should be "as sharing is not required"
slide 43: 푠푓표푙푑푙 (⊕) 푎 should be 푠푓표푙푑푙 (⊕) 푒
The Composite pattern allows hierarchical tree structures to be composed of objects. It allows clients to treat individual objects and compositions of objects uniformly. In the given document, the Composite pattern is described for representing graphical objects in a drawing editor. Graphic is an abstract component class that represents both individual graphical primitives like lines and text as well as container graphics like pictures. This allows clients to treat both types of graphics uniformly. The pattern provides structure for composing objects into tree structures and defines roles like component, leaf, and composite. It offers benefits like simplifying client code and making it easy to add new component types.
download for better quality - Learn how to use an Applicative Functor to handle multiple independent effectful values through the work of Sergei Winitzki, Runar Bjarnason, Paul Chiusano, Debasish Ghosh and Adelbert Chang
The document discusses design patterns including the Gang of Four book, UML class diagrams, and categories of design patterns such as creational, structural, and behavioral patterns. It provides definitions and examples of specific design patterns including abstract factory, factory method, observer, and bridge patterns. Key aspects like intent, participants, collaborations, and implementations are covered for some of the patterns.
The document discusses three design patterns: Singleton, Observer, and Factory. The Singleton pattern ensures that only one instance of a class can exist and provides a global access point. The Observer pattern defines a subscription mechanism so that multiple objects can be notified of changes to an object they are observing. The Factory pattern provides an interface for creating objects but leaves the concrete class unspecified. Real-world examples and implementations of each pattern are provided.
Creational patterns deal with object creation and aim to create objects in a suitable manner. There are three main creational patterns: the factory pattern, which provides a consistent interface for creating properly configured objects; the abstract factory pattern, which is a factory for factories and handles more complex situations; and the singleton pattern, which ensures a single instance of an object and consistency of data by restricting object instantiation.
The Proxy design pattern provides a surrogate or placeholder for another object to control access to it. A proxy can act as a placeholder for complex, expensive objects that should not be initialized until needed. This delays the creation of these objects until they are actually required. Proxies can also protect the real component from undue complexity or provide extra functionality. Some common uses of proxies include remote proxies for objects in a different address space, virtual proxies that create objects on demand, and protection proxies that control access to the original object.
Folding Unfolded - Polyglot FP for Fun and Profit - Haskell and Scala - Part ...Philip Schwarz
(download for best quality slides) Gain a deeper understanding of why right folds over very large and infinite lists are sometimes possible in Haskell.
See how lazy evaluation and function strictness affect left and right folds in Haskell.
Learn when an ordinary left fold results in a space leak and how to avoid it using a strict left fold.
This version eliminates some minor imperfections and corrects the following two errors:
slide 15: "as sharing is required" should be "as sharing is not required"
slide 43: 푠푓표푙푑푙 (⊕) 푎 should be 푠푓표푙푑푙 (⊕) 푒
The Composite pattern allows hierarchical tree structures to be composed of objects. It allows clients to treat individual objects and compositions of objects uniformly. In the given document, the Composite pattern is described for representing graphical objects in a drawing editor. Graphic is an abstract component class that represents both individual graphical primitives like lines and text as well as container graphics like pictures. This allows clients to treat both types of graphics uniformly. The pattern provides structure for composing objects into tree structures and defines roles like component, leaf, and composite. It offers benefits like simplifying client code and making it easy to add new component types.
This document provides an overview of design patterns, including their definition, origins, properties, types, and examples. It discusses common design patterns like Singleton, Observer, Strategy, Adapter, Facade, and Proxy. For each pattern, it describes the context, problem, forces, solution, and examples. The document also covers challenges of applying patterns and developing new patterns.
Software architectural patterns - A Quick Understanding GuideMohammed Fazuluddin
This document discusses various software architectural patterns. It begins by defining architectural patterns as general and reusable solutions to common software architecture problems within a given context. It then outlines 10 common patterns: layered, client-server, master-slave, pipe-filter, broker, peer-to-peer, event-bus, model-view-controller, blackboard, and interpreter. For each pattern, it briefly describes the pattern and provides examples of its usage. The document aims to provide a quick understanding of architectural patterns.
The proxy pattern provides a placeholder for another object to control access to it. A common use is for a local proxy to represent an object in a remote space. Here, a local help system acts as a proxy for an online help system. The local help is faster to access but less complete, while the online help is more robust but requires an internet connection. The proxy pattern allows the local help to be accessed first as a representative for the online help, providing faster access in many cases while retaining the ability to access the more full-featured online help when needed.
This document discusses design patterns and provides examples of the Singleton and Abstract Factory patterns. It begins with an introduction to design patterns, their purpose and history. It then discusses the Singleton pattern in detail using a logger example and describes how to implement it using lazy instantiation. It also covers the Abstract Factory pattern using real world examples of a kitchen and chefs. It compares how these patterns would be implemented in code versus real objects.
1. The OSFacade acts as a single interface for clients and delegates to subsystem facades.
2. Subsystem facades like ProcessFacade and IOFacade hide complexity and coordinate tasks.
3. Subsystem classes implement tasks while facade classes abstract complexity.
\n\nThe document discusses scalable JavaScript application architecture. It advocates for a modular approach where each component (module) has a limited, well-defined purpose and interface. Modules are loosely coupled by communicating through a central sandbox interface rather than directly referencing each other. The core application manages modules by registering, starting, and stopping them. It also handles errors and enables extension points. This architecture aims to build flexible, maintainable applications that can evolve over time.
‘go-to’ general-purpose sequential collections -from Java To ScalaPhilip Schwarz
The simple idea of this slide deck is that it collects in a single place quite a bit of information that can be used to gain a basic understanding of some key differences between the ’goto’ sequential collections of Java and Scala.
Errata:
* the twitter handle of Daniel Westheide is @kaffeecoder and not @cafeecoder
* on slide 35, the numbers 215, 220, 225 and 230, should be 2^15, 2^20, 2^25 and 2^30 respectively
* on slide 54, the bottom left node should be green rather than black
principles of object oriented class designNeetu Mishra
The document discusses the principles of object oriented class design known as SOLID principles, which are Single Responsibility Principle (SRP), Open Closed Principle (OCP), Liskov Substitution Principle (LSP), Interface Segregation Principle (ISP), and Dependency Inversion Principle (DIP). It provides examples and explanations of each principle, with SRP focusing on a single responsibility, OCP on extension without modification, LSP on substitutability of subclasses, ISP on specific interfaces over general interfaces, and DIP on depending on abstractions rather than concretions.
Download for better quality - Learn about the composition of effectful functions through the work of Bartosz Milewski, Rob Norris, Rúnar Bjarnason and Debasish Ghosh.
Here I link up up some very useful material by Robert Norris (@tpolecat) and Martin Odersky (@odersky) to introduce Monad laws and reinforce the importance of checking the laws. E.g. while Option satisfies the laws, Try is not a lawful Monad: it trades the left identity law for the bullet-proof principle.
* ERRATA *
1) on the first slide the Kleisli composition signature appears three times, but one of the occurrences is incorrect in that it is (>=>)::(a->mb)->(b->mb)->(a->mc) whereas is should be (>=>)::(a->mb)->(b->mc)->(a->mc) - thank you Jules Ivanic.
The composite design pattern allows clients to treat both individual objects and compositions of objects uniformly. It composes objects into tree structures to represent part-whole hierarchies. The pattern allows clients to ignore the difference between compositions of objects and individual objects. This makes it possible to treat both kinds of objects uniformly.
An introduction to creational design patterns in object orientation. Suitable for intermediate to advanced computing students and those studying software engineering.
The Collections Framework (java.util)- Collections overview, Collection Interfaces, The Collection classes- Array List, Linked List, Hash Set, Tree Set, Priority Queue, Array Deque. Accessing a Collection via an Iterator, Using an Iterator, The For-Each alternative, Map Interfaces and Classes, Comparators, Collection algorithms, Arrays, The Legacy Classes and Interfaces- Dictionary, Hashtable ,Properties, Stack, Vector More Utility classes, String Tokenizer, Bit Set, Date, Calendar, Random, Formatter, Scanner
The Adapter pattern is used to allow an existing Queue class to be used as a Stack. A ClassAdapter inherits from the Stack interface and the Queue class, implementing the stack operations by manipulating the underlying queue. An ObjectAdapter composes a Queue instance and implements the Stack interface by delegating to the queue's methods.
Neal Ford Emergent Design And Evolutionary ArchitectureThoughtworks
Neal Ford of ThoughtWorks discusses emergent design and evolutionary architecture. He argues that software design emerges from the code itself, rather than predefined documentation or specifications. Design is a process of discovering effective abstractions and patterns by refactoring code through practices like test-driven development. Architectural considerations involve balancing essential versus accidental complexity, technical debt, and flexibility to evolve designs over time in response to changing needs.
Here is how we can implement this using the Prototype pattern:
1. Define an abstract Car class with Clone() method
2. Define concrete classes like SportsCar, Sedan etc extending Car
3. Each car stores its design configuration as properties
4. Application maintains a registry of car prototypes
5. When user wants to copy, app clones the prototype and returns new instance
6. User can independently customize cloned car without affecting original
This allows dynamic object copying and meets the requirements. Prototype pattern helps avoid complex object creation and gives flexibility to user for experimenting with different designs efficiently.
in these slides i have explained the difference between MVC, MVP and MVVM design patterns. slides includes definition, explanation and then implementation with code examples. it is a comparison oriented presentation.
Stanko introduces GraphQL as an alternative to REST for building APIs. Some key problems with REST include over-fetching of data and lack of standardized documentation. GraphQL addresses these by allowing clients to specify exactly which attributes they need in a query. The response then matches the structure of the query. GraphQL also provides automatic documentation of available queries and mutations through an introspection system. Overall, GraphQL provides a more flexible way to retrieve resources from an API compared to REST.
This time I want to speak on the 'printf' function. Everybody has heard of software vulnerabilities and that functions like 'printf' are outlaw. But it's one thing to know that you'd better not use these functions, and quite the other to understand why. In this article, I will describe two classic software vulnerabilities related to 'printf'. You won't become a hacker after that but perhaps you will have a fresh look at your code. You might create similar vulnerable functions in your project without knowing that.
This document provides an overview of design patterns, including their definition, origins, properties, types, and examples. It discusses common design patterns like Singleton, Observer, Strategy, Adapter, Facade, and Proxy. For each pattern, it describes the context, problem, forces, solution, and examples. The document also covers challenges of applying patterns and developing new patterns.
Software architectural patterns - A Quick Understanding GuideMohammed Fazuluddin
This document discusses various software architectural patterns. It begins by defining architectural patterns as general and reusable solutions to common software architecture problems within a given context. It then outlines 10 common patterns: layered, client-server, master-slave, pipe-filter, broker, peer-to-peer, event-bus, model-view-controller, blackboard, and interpreter. For each pattern, it briefly describes the pattern and provides examples of its usage. The document aims to provide a quick understanding of architectural patterns.
The proxy pattern provides a placeholder for another object to control access to it. A common use is for a local proxy to represent an object in a remote space. Here, a local help system acts as a proxy for an online help system. The local help is faster to access but less complete, while the online help is more robust but requires an internet connection. The proxy pattern allows the local help to be accessed first as a representative for the online help, providing faster access in many cases while retaining the ability to access the more full-featured online help when needed.
This document discusses design patterns and provides examples of the Singleton and Abstract Factory patterns. It begins with an introduction to design patterns, their purpose and history. It then discusses the Singleton pattern in detail using a logger example and describes how to implement it using lazy instantiation. It also covers the Abstract Factory pattern using real world examples of a kitchen and chefs. It compares how these patterns would be implemented in code versus real objects.
1. The OSFacade acts as a single interface for clients and delegates to subsystem facades.
2. Subsystem facades like ProcessFacade and IOFacade hide complexity and coordinate tasks.
3. Subsystem classes implement tasks while facade classes abstract complexity.
\n\nThe document discusses scalable JavaScript application architecture. It advocates for a modular approach where each component (module) has a limited, well-defined purpose and interface. Modules are loosely coupled by communicating through a central sandbox interface rather than directly referencing each other. The core application manages modules by registering, starting, and stopping them. It also handles errors and enables extension points. This architecture aims to build flexible, maintainable applications that can evolve over time.
‘go-to’ general-purpose sequential collections -from Java To ScalaPhilip Schwarz
The simple idea of this slide deck is that it collects in a single place quite a bit of information that can be used to gain a basic understanding of some key differences between the ’goto’ sequential collections of Java and Scala.
Errata:
* the twitter handle of Daniel Westheide is @kaffeecoder and not @cafeecoder
* on slide 35, the numbers 215, 220, 225 and 230, should be 2^15, 2^20, 2^25 and 2^30 respectively
* on slide 54, the bottom left node should be green rather than black
principles of object oriented class designNeetu Mishra
The document discusses the principles of object oriented class design known as SOLID principles, which are Single Responsibility Principle (SRP), Open Closed Principle (OCP), Liskov Substitution Principle (LSP), Interface Segregation Principle (ISP), and Dependency Inversion Principle (DIP). It provides examples and explanations of each principle, with SRP focusing on a single responsibility, OCP on extension without modification, LSP on substitutability of subclasses, ISP on specific interfaces over general interfaces, and DIP on depending on abstractions rather than concretions.
Download for better quality - Learn about the composition of effectful functions through the work of Bartosz Milewski, Rob Norris, Rúnar Bjarnason and Debasish Ghosh.
Here I link up up some very useful material by Robert Norris (@tpolecat) and Martin Odersky (@odersky) to introduce Monad laws and reinforce the importance of checking the laws. E.g. while Option satisfies the laws, Try is not a lawful Monad: it trades the left identity law for the bullet-proof principle.
* ERRATA *
1) on the first slide the Kleisli composition signature appears three times, but one of the occurrences is incorrect in that it is (>=>)::(a->mb)->(b->mb)->(a->mc) whereas is should be (>=>)::(a->mb)->(b->mc)->(a->mc) - thank you Jules Ivanic.
The composite design pattern allows clients to treat both individual objects and compositions of objects uniformly. It composes objects into tree structures to represent part-whole hierarchies. The pattern allows clients to ignore the difference between compositions of objects and individual objects. This makes it possible to treat both kinds of objects uniformly.
An introduction to creational design patterns in object orientation. Suitable for intermediate to advanced computing students and those studying software engineering.
The Collections Framework (java.util)- Collections overview, Collection Interfaces, The Collection classes- Array List, Linked List, Hash Set, Tree Set, Priority Queue, Array Deque. Accessing a Collection via an Iterator, Using an Iterator, The For-Each alternative, Map Interfaces and Classes, Comparators, Collection algorithms, Arrays, The Legacy Classes and Interfaces- Dictionary, Hashtable ,Properties, Stack, Vector More Utility classes, String Tokenizer, Bit Set, Date, Calendar, Random, Formatter, Scanner
The Adapter pattern is used to allow an existing Queue class to be used as a Stack. A ClassAdapter inherits from the Stack interface and the Queue class, implementing the stack operations by manipulating the underlying queue. An ObjectAdapter composes a Queue instance and implements the Stack interface by delegating to the queue's methods.
Neal Ford Emergent Design And Evolutionary ArchitectureThoughtworks
Neal Ford of ThoughtWorks discusses emergent design and evolutionary architecture. He argues that software design emerges from the code itself, rather than predefined documentation or specifications. Design is a process of discovering effective abstractions and patterns by refactoring code through practices like test-driven development. Architectural considerations involve balancing essential versus accidental complexity, technical debt, and flexibility to evolve designs over time in response to changing needs.
Here is how we can implement this using the Prototype pattern:
1. Define an abstract Car class with Clone() method
2. Define concrete classes like SportsCar, Sedan etc extending Car
3. Each car stores its design configuration as properties
4. Application maintains a registry of car prototypes
5. When user wants to copy, app clones the prototype and returns new instance
6. User can independently customize cloned car without affecting original
This allows dynamic object copying and meets the requirements. Prototype pattern helps avoid complex object creation and gives flexibility to user for experimenting with different designs efficiently.
in these slides i have explained the difference between MVC, MVP and MVVM design patterns. slides includes definition, explanation and then implementation with code examples. it is a comparison oriented presentation.
Stanko introduces GraphQL as an alternative to REST for building APIs. Some key problems with REST include over-fetching of data and lack of standardized documentation. GraphQL addresses these by allowing clients to specify exactly which attributes they need in a query. The response then matches the structure of the query. GraphQL also provides automatic documentation of available queries and mutations through an introspection system. Overall, GraphQL provides a more flexible way to retrieve resources from an API compared to REST.
This time I want to speak on the 'printf' function. Everybody has heard of software vulnerabilities and that functions like 'printf' are outlaw. But it's one thing to know that you'd better not use these functions, and quite the other to understand why. In this article, I will describe two classic software vulnerabilities related to 'printf'. You won't become a hacker after that but perhaps you will have a fresh look at your code. You might create similar vulnerable functions in your project without knowing that.
The document discusses the speaker's experience learning new technologies and tools over their career as a geospatial technologist. It describes how learning the FME tool early on helped shape how they approach problems and expanded what they saw as possible by showing data is more like a liquid than a solid. It also taught them that good tools should integrate easily and introduced them to declarative programming models. This led them to learn other technologies like SQL, TCL, and eventually experiment with spatial databases.
This document is an introductory tutorial on PHP web development. It begins by explaining what PHP is and its role in server-side programming. It then covers PHP basics like variables, arrays, functions, loops and conditional statements. Examples are provided for each concept to demonstrate how it works in PHP code. The document concludes by providing a full program example that combines these elements to check names in an array against a condition using a loop and function.
The Sieve of Eratosthenes - Part 1 - with minor correctionsPhilip Schwarz
In this slide deck we are going to see some examples of how the effort required to read an understand the Sieve of Eratosthenes varies greatly depending on the programming paradigm used to implement the algorithm.
We'll see code in Java, Scheme, Haskell and Scala.
This version of the deck contains some minor corrections (see errata section in first version for details)
In this slide deck we are going to see some examples of how the effort required to read an understand the Sieve of Eratosthenes varies greatly depending on the programming paradigm used to implement the algorithm.
We'll see code in Java, Scheme, Haskell and Scala.
Errata:
slide 10: "points asserts" should be "asserts"
slide 22,23,25: "// larger than that root.private static int" should be "// larger than that root"
slide 29: "three slides" should be "two slides"
slide 45,46,48,49: List.range(m,n + 1) should be List.range(2,maxValue + 1)
The document discusses how local variables are allocated in the stack and how compilers handle it. It explains that local variables are stored in the stack frame of a function. When the main function is called, space for the local variable i is allocated in the stack. The compiler generates code to initialize i to 10 by loading 10 into a register and storing it in i's location. When printf is called, it expects an integer argument following the format specifier "%d", and it reads this argument from a register. Most compilers reuse registers, so it's possible that printf reads from the same register used to initialize i, resulting in 10 being printed instead of garbage. The document also demonstrates how local variables may be allocated contiguously in the stack through
React is a different way to write JavaScript apps. When it was introduced at JSConf US in May, the audience was shocked by some of its design principles. One sarcastic tweet from an audience member ended up describing React’s philosophy quite accurately: https://twitter.com/cowboy/status/339858717451362304
We’re trying to push the limits of what’s possible on the web with React. My talk will start with a brief introduction to the framework, and then dive into three controversial topics: Throwing out the notion of templates and building views with JavaScript, “re-rendering” your entire application when your data changes, and a lightweight implementation of the DOM and events.
The document discusses different approaches to software testing including Behavior Driven Development (BDD), test-driven development (TDD), and unit testing frameworks. It introduces BDD as a process that focuses on specifying what functionality software should provide from the perspective of end users and domain experts. BDD tools like Cucumber and RSpec are discussed as ways to write automated tests in a readable language that can be understood by non-technical stakeholders. Mocking libraries like Mocha are also presented as a way to isolate layers and collaborations when testing code.
LabsLab8.htmlLab 8 Im Thinking of a NumberBefore yo.docxDIPESH30
Labs/Lab8.html
Lab 8: I'm Thinking of a Number
Before you begin this lab please review Javascript from the lecture notes.
This lab is meant to help you learn the rudiments of the Javascript programming language and understand something of how web pages use Javascript, well enough that you can write a basic Javascript program that implements a simple game. You will also begin to develop some appreciation of why programming is not entirely trivial. It really does require orderly thinking and meticulous attention to detail.
Please read these instructions before beginning the lab.
Please follow the instructions about program format, variable names, etc.
Please use the template in Part 3.
Please pay attention to syntax and grammar and language constructs.
You will have a better chance of success if you follow the highlighted hints. Things will work better if you do, and you may even find that programming is kind of fun, especially when your program works.
You can do this lab anywhere. Remember to post questions in the forum where you can get help from each other.Part 1: IntroductionPart 2: The Javascript LanguagePart 3: Writing your own Javascript ProgramPart 4: Finishing up
Part 1: Introduction - PREAMBLE 1
Programming languages provide a way to express computer algorithms in a form that is convenient for humans yet easily translated into a computer's machine language: programming languages are the way that we tell computers how to perform a task.
In the lecture notes, we have studied the very low-level instructions that the computer itself understands (for example, the Toy), and talked about a variety of programming languages, much easier for people to use, that are translated into machine instructions by programs like compilers and assemblers. There are many such languages, each with its own good and bad points, and often with noisy adherents and detractors.
Javascript, the topic of this lab and the next, is one of the most widely encountered languages, largely because it's available as part of every Web browser, and the majority of web pages include Javascript code. You too can write Javascript programs that will be run by whoever views your web page. We don't expect you to become a full-fledged Javascript programmer, but you will do enough in this lab and the next to get some understanding of what programs look like and what is involved in taking an algorithm and turning it into a program that implements the algorithm.
You'll also be able to better understand the Javascript pages that you encounter when you browse, and if you like, you'll be able to adapt and modify them for your own pages too.
There is an enormous amount of Javascript information on the Web, and thousands of books. You might start with this list of tutorials, or Google for "javascript tutorial".
Javascript has three major components:the Javascript language itselfbuilding blocks that you can use to create your programmethods that let your Javascript program inter ...
The document discusses an employee who left the company to work for an embedded systems company. The employee was concerned about the poor code quality at the new company. The employee is now trying to improve the code quality by introducing concepts like static analysis and version control that were emphasized at the previous company. The document includes examples of common coding issues and a paper the employee wrote to address these issues at the new company. The conclusion expresses hope that the situation is improving at the new company but also sadness that many programmers at large companies are unaware of modern development practices.
This guide provides a refresher on basic computer programming concepts without using a specific programming language. It defines key terms like variables, which represent values that can change throughout a program, and statements, which are the smallest standalone elements a computer can understand. It also explains functions and methods as named sets of instructions that can be reused, and parameters as values passed into functions. Finally, it outlines different data types like integers, doubles, strings, and booleans that variables can take on to store different kinds of values.
This document discusses how organizations can transform by tightening feedback loops. It provides examples of feedback loops throughout software development processes like requirements, development, testing, and release cycles. Tightening these loops can reduce risk, increase speed, and improve value for customers. While change can be difficult, starting with small improvements and anticipating ripple effects can help drive successful transformation over time. The goal is to fail faster, learn sooner, and continuously improve.
Understand the expression problem
See Haskell and Scala code illustrating the problem
Learn how FP typeclasses can be used to solve the problem
See the Haskell solution to the problem and a translation into Scala
Part 2
This document introduces JavaScript and its use in web pages. It discusses embedding JavaScript code within HTML using <script> tags, and how JavaScript interacts with and manipulates the DOM (Document Object Model). It explains that all HTML elements are represented as objects in JavaScript that can be accessed and modified. It provides an example HTML page and illustrates how to navigate the hierarchy of DOM objects to access and change specific elements like forms and images.
GPT and other Text Transformers: Black Swans and Stochastic ParrotsKonstantin Savenkov
Over the last year, we see increasingly more performant Text Transformers models, such as GPT-3 from OpenAI, Turing from Microsoft, and T5 from Google. They are capable of transforming the text in very creative and unexpected ways, like generating a summary of an article, explaining complex concepts in a simple language, or synthesizing realistic datasets for AI training. Unlike more traditional Machine Learning models, they do not require vast training datasets and can start based on just a few examples.
In this talk, we will make a short overview of such models, share the first experimental results and ask questions about the future of the content creation process. Are those models ready for prime time? What will happen to the professional content creators? Will they be able to compete against such powerful models? Will we see GPT post-editing similar to MT post-editing? We will share some answers we have based on the extensive experimenting and the first production projects that employ this new technology.
The document discusses next-generation programming languages that run on the Java Virtual Machine (JVM), focusing on Groovy and the Grails web application framework. It provides an overview of Groovy's features like closures and first-class containers. It also demonstrates building a sample web application in Groovy/Grails and discusses how Groovy integrates seamlessly with Java.
Solutions manual for absolute java 5th edition by walter savitchAlbern9271
Solutions manual for absolute java 5th edition by walter savitch
Full clear download( no error formatting) at:
https://goo.gl/Aic8JR
absolute java 5th edition pdf free
absolute java 5th edition by walter savitch pdf
absolute java 5th edition solutions pdf
absolute java 6th edition solutions pdf
absolute java programming projects solutions
pearson absolute java 5th ed walter savitch 2012 pdf
walter savitch absolute java 4th edition or newer addison wesley 3rd edition is also fine
Slides: http://pa4373.github.io/jstutorials_corepart/
GitHub Repo: https://github.com/pa4373/jstutorials_corepart
This is the first part of JavaScript programming tutorials. This tutorial introduces the brief history of JavaScript, the relationship of the specification and a variety of implementations, and then the basic syntax and the concepts are introduced, that can be generally applied to programming interactions with different runtimes.
Similar to Refactoring: A First Example - Martin Fowler’s First Example of Refactoring, Adapted to Java (20)
Folding Cheat Sheet #6 - sixth in a seriesPhilip Schwarz
Left and right folds and tail recursion.
Errata: there are some errors on slide 4. See here for a corrected versionsof the deck:
https://speakerdeck.com/philipschwarz/folding-cheat-sheet-number-6
https://fpilluminated.com/deck/227
Hand Rolled Applicative User ValidationCode KataPhilip Schwarz
Could you use a simple piece of Scala validation code (granted, a very simplistic one too!) that you can rewrite, now and again, to refresh your basic understanding of Applicative operators <*>, <*, *>?
The goal is not to write perfect code showcasing validation, but rather, to provide a small, rough-and ready exercise to reinforce your muscle-memory.
Despite its grandiose-sounding title, this deck consists of just three slides showing the Scala 3 code to be rewritten whenever the details of the operators begin to fade away.
The code is my rough and ready translation of a Haskell user-validation program found in a book called Finding Success (and Failure) in Haskell - Fall in love with applicative functors.
Direct Style Effect Systems -The Print[A] Example- A Comprehension AidPhilip Schwarz
The subject of this deck is the small Print[A] program in the following blog post by Noel Welsh: https://www.inner-product.com/posts/direct-style-effects/.
Keywords: "direct-style", "context function", "context functions", "algebraic effect", "algebraic effects", "scala", "effect system", "effect systems", "effect", "side effect", "composition", "fp", "functional programming"
Folding Cheat Sheet #4 - fourth in a seriesPhilip Schwarz
For functions that can be defined both as an instance of a right fold and as an instance of a left fold, one may be more efficient than the other.
Let's look at the example of a function 'decimal' that converts a list of digits into the corresponding decimal number.
Erratum: it has been pointed out that it is possible to define the zip function using a right fold (see slide 5).
Folding Cheat Sheet #3 - third in a seriesPhilip Schwarz
This document summarizes the universal property of fold, which states that for finite lists the fold function is the unique function that satisfies its defining recursive equations. It provides examples of how common list functions like sum, product, length, concatenation (⧺) can be defined in terms of fold. It also notes that the universal property can be generalized to handle partial and infinite lists. Finally, it states that map, filter and fold form the "triad" or basic building blocks of functional programming.
Folding Cheat Sheet #2 - second in a seriesPhilip Schwarz
This document provides definitions and examples of foldr and foldl functions in functional programming. Foldr recursively applies a function from right to left, associating at each step. Foldl recursively applies a function from left to right, associating at each step. Examples are given applying foldr and foldl to a list with elements a0 through a3, using a function f and initial value b. Programmatic and mathematical definitions of foldr and foldl are also presented.
Folding Cheat Sheet #1 - first in a seriesPhilip Schwarz
This document provides examples of using fold functions to define recursive functions over natural numbers (Nat) and lists. It shows common patterns for defining recursive functions over Nat using foldn and over lists using foldr. Three examples are given of functions defined over Nat using foldn: addition, multiplication, and exponentiation. Three examples are also given of functions defined over lists using foldr: sum, length, and append.
Tagless Final Encoding - Algebras and Interpreters and also ProgramsPhilip Schwarz
Tagless Final Encoding - Algebras and Interpreters and also Programs - An introduction, through the work of Gabriel Volpe.
Slide deck home: http://fpilluminated.com/assets/tagless-final-encoding-algebras-interpreters-and-programs.html
A sighting of traverseFilter and foldMap in Practical FP in ScalaPhilip Schwarz
Slide deck home: http://fpilluminated.com/assets/sighting-of-scala-cats-traverseFilter-and-foldMap-in-practical-fp-in-scala.html.
Download PDF for perfect image quality.
A sighting of sequence function in Practical FP in ScalaPhilip Schwarz
Slide deck home: http://fpilluminated.com/assets/sighting-of-scala-cats-sequence-function-in-practical-fp-in-scala.html.
Download PDF for perfect image quality.
This talk was presented on Aug 3rd 2023 during the Scala in the City event a ITV in London https://www.meetup.com/scala-in-the-city/events/292844968/
Visit the following for a description, slideshow, all slides with transcript, pdf, github repo, and eventually a video recording: http://fpilluminated.com/assets/n-queens-combinatorial-puzzle-meets-cats.html
At the centre of this talk is the N-Queens combinatorial puzzle. The reason why this puzzle features in the Scala book and functional programming course by Martin Odersky (the language’s creator), is that such puzzles are a particularly suitable application area of 'for comprehensions'.
We’ll start by (re)acquainting ourselves with the puzzle, and seeing the role played in it by permutations. Next, we’ll see how, when wanting to visualise candidate puzzle solutions, Cats’ monoidal functions fold and foldMap are a great fit for combining images.
While we are all very familiar with the triad providing the bread, butter and jam of functional programming, i.e. map, filter and fold, not everyone knows about the corresponding functions in Cats’ monadic variant of the triad, i.e. mapM, filterM and foldM, which we are going to learn about next.
As is often the case in functional programming, the traverse function makes an appearance, and we shall grab the opportunity to point out the symmetry that exists in the interrelation of flatMap / foldMap / traverse and flatten / fold / sequence.
Armed with an understanding of foldM, we then look at how such a function can be used to implement an iterative algorithm for the N-Queens puzzle.
The talk ends by pointing out that the iterative algorithm is smarter than the recursive one, because it ‘remembers’ where it has already placed previous queens.
Kleisli composition, flatMap, join, map, unit - implementation and interrelat...Philip Schwarz
Kleisli composition, flatMap, join, map, unit. A study/memory aid, to help learn/recall their implementation/interrelation.
Version 2, updated for Scala 3
Nat, List and Option Monoids -from scratch -Combining and Folding -an examplePhilip Schwarz
Nat, List and Option Monoids, from scratch. Combining and Folding: an example.
This is a new version of the original which has some cosmetic changes and a new 7th slide which only differs from slide 6 in that it defines the fold function in terms of the foldRight function.
Code: https://github.com/philipschwarz/nat-list-and-option-monoids-from-scratch-combining-and-folding-an-example
Nat, List and Option Monoids -from scratch -Combining and Folding -an examplePhilip Schwarz
Nat, List and Option Monoids, from scratch. Combining and Folding: an example.
Code: https://github.com/philipschwarz/nat-list-and-option-monoids-from-scratch-combining-and-folding-an-example
The Sieve of Eratosthenes - Part II - Genuine versus Unfaithful Sieve - Haske...Philip Schwarz
When I posted the deck for Part 1 to the Scala users forum, Odd Möller linked to a paper titled "The Genuine Sieve of Eratosthenes", which speaks of the Unfaithful Sieve.
Part 2 is based on that paper and on Richard Bird's faithful Haskell implementation of the Sieve, which we translate into Scala.
Scala code for Richard Bird's infinite primes Haskell program: https://github.com/philipschwarz/sieve-of-eratosthenes-part-2-scala
Alluxio Webinar | 10x Faster Trino Queries on Your Data PlatformAlluxio, Inc.
Alluxio Webinar
June. 18, 2024
For more Alluxio Events: https://www.alluxio.io/events/
Speaker:
- Jianjian Xie (Staff Software Engineer, Alluxio)
As Trino users increasingly rely on cloud object storage for retrieving data, speed and cloud cost have become major challenges. The separation of compute and storage creates latency challenges when querying datasets; scanning data between storage and compute tiers becomes I/O bound. On the other hand, cloud API costs related to GET/LIST operations and cross-region data transfer add up quickly.
The newly introduced Trino file system cache by Alluxio aims to overcome the above challenges. In this session, Jianjian will dive into Trino data caching strategies, the latest test results, and discuss the multi-level caching architecture. This architecture makes Trino 10x faster for data lakes of any scale, from GB to EB.
What you will learn:
- Challenges relating to the speed and costs of running Trino in the cloud
- The new Trino file system cache feature overview, including the latest development status and test results
- A multi-level cache framework for maximized speed, including Trino file system cache and Alluxio distributed cache
- Real-world cases, including a large online payment firm and a top ridesharing company
- The future roadmap of Trino file system cache and Trino-Alluxio integration
WMF 2024 - Unlocking the Future of Data Powering Next-Gen AI with Vector Data...Luigi Fugaro
Vector databases are transforming how we handle data, allowing us to search through text, images, and audio by converting them into vectors. Today, we'll dive into the basics of this exciting technology and discuss its potential to revolutionize our next-generation AI applications. We'll examine typical uses for these databases and the essential tools
developers need. Plus, we'll zoom in on the advanced capabilities of vector search and semantic caching in Java, showcasing these through a live demo with Redis libraries. Get ready to see how these powerful tools can change the game!
Ensuring Efficiency and Speed with Practical Solutions for Clinical OperationsOnePlan Solutions
Clinical operations professionals encounter unique challenges. Balancing regulatory requirements, tight timelines, and the need for cross-functional collaboration can create significant internal pressures. Our upcoming webinar will introduce key strategies and tools to streamline and enhance clinical development processes, helping you overcome these challenges.
The Power of Visual Regression Testing_ Why It Is Critical for Enterprise App...kalichargn70th171
Visual testing plays a vital role in ensuring that software products meet the aesthetic requirements specified by clients in functional and non-functional specifications. In today's highly competitive digital landscape, users expect a seamless and visually appealing online experience. Visual testing, also known as automated UI testing or visual regression testing, verifies the accuracy of the visual elements that users interact with.
A Comprehensive Guide on Implementing Real-World Mobile Testing Strategies fo...kalichargn70th171
In today's fiercely competitive mobile app market, the role of the QA team is pivotal for continuous improvement and sustained success. Effective testing strategies are essential to navigate the challenges confidently and precisely. Ensuring the perfection of mobile apps before they reach end-users requires thoughtful decisions in the testing plan.
Why Apache Kafka Clusters Are Like Galaxies (And Other Cosmic Kafka Quandarie...Paul Brebner
Closing talk for the Performance Engineering track at Community Over Code EU (Bratislava, Slovakia, June 5 2024) https://eu.communityovercode.org/sessions/2024/why-apache-kafka-clusters-are-like-galaxies-and-other-cosmic-kafka-quandaries-explored/ Instaclustr (now part of NetApp) manages 100s of Apache Kafka clusters of many different sizes, for a variety of use cases and customers. For the last 7 years I’ve been focused outwardly on exploring Kafka application development challenges, but recently I decided to look inward and see what I could discover about the performance, scalability and resource characteristics of the Kafka clusters themselves. Using a suite of Performance Engineering techniques, I will reveal some surprising discoveries about cosmic Kafka mysteries in our data centres, related to: cluster sizes and distribution (using Zipf’s Law), horizontal vs. vertical scalability, and predicting Kafka performance using metrics, modelling and regression techniques. These insights are relevant to Kafka developers and operators.
A neural network is a machine learning program, or model, that makes decisions in a manner similar to the human brain, by using processes that mimic the way biological neurons work together to identify phenomena, weigh options and arrive at conclusions.
Building API data products on top of your real-time data infrastructureconfluent
This talk and live demonstration will examine how Confluent and Gravitee.io integrate to unlock value from streaming data through API products.
You will learn how data owners and API providers can document, secure data products on top of Confluent brokers, including schema validation, topic routing and message filtering.
You will also see how data and API consumers can discover and subscribe to products in a developer portal, as well as how they can integrate with Confluent topics through protocols like REST, Websockets, Server-sent Events and Webhooks.
Whether you want to monetize your real-time data, enable new integrations with partners, or provide self-service access to topics through various protocols, this webinar is for you!
The Rising Future of CPaaS in the Middle East 2024Yara Milbes
Explore "The Rising Future of CPaaS in the Middle East in 2024" with this comprehensive PPT presentation. Discover how Communication Platforms as a Service (CPaaS) is transforming communication across various sectors in the Middle East.
Enhanced Screen Flows UI/UX using SLDS with Tom KittPeter Caitens
Join us for an engaging session led by Flow Champion, Tom Kitt. This session will dive into a technique of enhancing the user interfaces and user experiences within Screen Flows using the Salesforce Lightning Design System (SLDS). This technique uses Native functionality, with No Apex Code, No Custom Components and No Managed Packages required.
Streamlining End-to-End Testing Automation with Azure DevOps Build & Release Pipelines
Automating end-to-end (e2e) test for Android and iOS native apps, and web apps, within Azure build and release pipelines, poses several challenges. This session dives into the key challenges and the repeatable solutions implemented across multiple teams at a leading Indian telecom disruptor, renowned for its affordable 4G/5G services, digital platforms, and broadband connectivity.
Challenge #1. Ensuring Test Environment Consistency: Establishing a standardized test execution environment across hundreds of Azure DevOps agents is crucial for achieving dependable testing results. This uniformity must seamlessly span from Build pipelines to various stages of the Release pipeline.
Challenge #2. Coordinated Test Execution Across Environments: Executing distinct subsets of tests using the same automation framework across diverse environments, such as the build pipeline and specific stages of the Release Pipeline, demands flexible and cohesive approaches.
Challenge #3. Testing on Linux-based Azure DevOps Agents: Conducting tests, particularly for web and native apps, on Azure DevOps Linux agents lacking browser or device connectivity presents specific challenges in attaining thorough testing coverage.
This session delves into how these challenges were addressed through:
1. Automate the setup of essential dependencies to ensure a consistent testing environment.
2. Create standardized templates for executing API tests, API workflow tests, and end-to-end tests in the Build pipeline, streamlining the testing process.
3. Implement task groups in Release pipeline stages to facilitate the execution of tests, ensuring consistency and efficiency across deployment phases.
4. Deploy browsers within Docker containers for web application testing, enhancing portability and scalability of testing environments.
5. Leverage diverse device farms dedicated to Android, iOS, and browser testing to cover a wide range of platforms and devices.
6. Integrate AI technology, such as Applitools Visual AI and Ultrafast Grid, to automate test execution and validation, improving accuracy and efficiency.
7. Utilize AI/ML-powered central test automation reporting server through platforms like reportportal.io, providing consolidated and real-time insights into test performance and issues.
These solutions not only facilitate comprehensive testing across platforms but also promote the principles of shift-left testing, enabling early feedback, implementing quality gates, and ensuring repeatability. By adopting these techniques, teams can effectively automate and execute tests, accelerating software delivery while upholding high-quality standards across Android, iOS, and web applications.
Penify - Let AI do the Documentation, you write the Code.KrishnaveniMohan1
Penify automates the software documentation process for Git repositories. Every time a code modification is merged into "main", Penify uses a Large Language Model to generate documentation for the updated code. This automation covers multiple documentation layers, including InCode Documentation, API Documentation, Architectural Documentation, and PR documentation, each designed to improve different aspects of the development process. By taking over the entire documentation process, Penify tackles the common problem of documentation becoming outdated as the code evolves.
https://www.penify.dev/
Orca: Nocode Graphical Editor for Container OrchestrationPedro J. Molina
Tool demo on CEDI/SISTEDES/JISBD2024 at A Coruña, Spain. 2024.06.18
"Orca: Nocode Graphical Editor for Container Orchestration"
by Pedro J. Molina PhD. from Metadev
Photoshop Tutorial for Beginners (2024 Edition)alowpalsadig
Photoshop Tutorial for Beginners (2024 Edition)
Explore the evolution of programming and software development and design in 2024. Discover emerging trends shaping the future of coding in our insightful analysis."
Here's an overview:Introduction: The Evolution of Programming and Software DevelopmentThe Rise of Artificial Intelligence and Machine Learning in CodingAdopting Low-Code and No-Code PlatformsQuantum Computing: Entering the Software Development MainstreamIntegration of DevOps with Machine Learning: MLOpsAdvancements in Cybersecurity PracticesThe Growth of Edge ComputingEmerging Programming Languages and FrameworksSoftware Development Ethics and AI RegulationSustainability in Software EngineeringThe Future Workforce: Remote and Distributed TeamsConclusion: Adapting to the Changing Software Development LandscapeIntroduction: The Evolution of Programming and Software Development
Photoshop Tutorial for Beginners (2024 Edition)Explore the evolution of programming and software development and design in 2024. Discover emerging trends shaping the future of coding in our insightful analysis."Here's an overview:Introduction: The Evolution of Programming and Software DevelopmentThe Rise of Artificial Intelligence and Machine Learning in CodingAdopting Low-Code and No-Code PlatformsQuantum Computing: Entering the Software Development MainstreamIntegration of DevOps with Machine Learning: MLOpsAdvancements in Cybersecurity PracticesThe Growth of Edge ComputingEmerging Programming Languages and FrameworksSoftware Development Ethics and AI RegulationSustainability in Software EngineeringThe Future Workforce: Remote and Distributed TeamsConclusion: Adapting to the Changing Software Development LandscapeIntroduction: The Evolution of Programming and Software Development
The importance of developing and designing programming in 2024
Programming design and development represents a vital step in keeping pace with technological advancements and meeting ever-changing market needs. This course is intended for anyone who wants to understand the fundamental importance of software development and design, whether you are a beginner or a professional seeking to update your knowledge.
Course objectives:
1. **Learn about the basics of software development:
- Understanding software development processes and tools.
- Identify the role of programmers and designers in software projects.
2. Understanding the software design process:
- Learn about the principles of good software design.
- Discussing common design patterns such as Object-Oriented Design.
3. The importance of user experience (UX) in modern software:
- Explore how user experience can improve software acceptance and usability.
- Tools and techniques to analyze and improve user experience.
4. Increase efficiency and productivity through modern development tools:
- Access to the latest programming tools and languages used in the industry.
- Study live examples of applications
14 th Edition of International conference on computer visionShulagnaSarkar2
About the event
14th Edition of International conference on computer vision
Computer conferences organized by ScienceFather group. ScienceFather takes the privilege to invite speakers participants students delegates and exhibitors from across the globe to its International Conference on computer conferences to be held in the Various Beautiful cites of the world. computer conferences are a discussion of common Inventions-related issues and additionally trade information share proof thoughts and insight into advanced developments in the science inventions service system. New technology may create many materials and devices with a vast range of applications such as in Science medicine electronics biomaterials energy production and consumer products.
Nomination are Open!! Don't Miss it
Visit: computer.scifat.com
Award Nomination: https://x-i.me/ishnom
Conference Submission: https://x-i.me/anicon
For Enquiry: Computer@scifat.com
14 th Edition of International conference on computer vision
Refactoring: A First Example - Martin Fowler’s First Example of Refactoring, Adapted to Java
1. Refactoring: A First Example
Martin Fowler’s First Example of Refactoring, Adapted to Java
follow in the footsteps of refactoring guru Martin Fowler
as he improves the design of a program in a simple yet instructive refactoring example
whose JavaScript code and associated refactoring is herein adapted to Java
based on the second edition of ‘the’ Refactoring book
Martin Fowler
@martinfowler
@philip_schwarz
slides by https://www.slideshare.net/pjschwarz
2. @philip_schwarz
Neither Martin Fowler nor the Refactoring book need any introduction.
I have always been a great fan of both, and having finally found the time to study in detail the refactoring
example in the second edition of the book, I would like to share the experience of adapting to Java such
a useful example, which happens to be written in JavaScript.
Another reason for looking in detail at the example is that it can be used as a good refactoring code kata.
While we’ll be closely following Martin Fowler’s footsteps as he works through the refactoring example,
and while those of you who don’t already own a copy of the book will no doubt learn a lot about the
chapter containing the example, what we’ll see is obviously only a small part of what makes the book
such a must have for anyone interested in refactoring.
The next four slides consist of excerpts in which Martin Fowler introduces the program whose design he
will be improving through refactoring.
3. So I’m going to start this book with an example of refactoring. I’ll talk about how refactoring works and will give you a sense of the refactoring process. I can
then do the usual principles-style introduction in the next chapter.
With any introductory example, however, I run into a problem. If I pick a large program, describing it and how it is refactored is too complicated for a mortal
reader to work through. (I tried this with the original book—and ended up throwing away two examples, which were still pretty small but took over a hundred
pages each to describe.) However, if I pick a program that is small enough to be comprehensible, refactoring does not look like it is worthwhile.
I’m thus in the classic bind of anyone who wants to describe techniques that are useful for real-world programs.
Frankly, it is not worth the effort to do all the refactoring that I’m going to show you on the small program I will be using.
But if the code I’m showing you is part of a larger system, then the refactoring becomes important. Just look at my example and imagine it in the context of a
much larger system.
I chose JavaScript to illustrate these refactorings, as I felt that this language would be readable by the most amount of people.
You shouldn’t find it difficult, however, to adapt the refactorings to whatever language you are currently using.
I try not to use any of the more complicated bits of the language, so you should be able to follow the refactorings with only a cursory knowledge of JavaScript.
My use of JavaScript is certainly not an endorsement of the language.
Although I use JavaScript for my examples, that doesn’t mean the techniques in this book are confined to JavaScript.
The first edition of this book used Java, and many programmers found it useful even though they never wrote a single Java class.
I did toy with illustrating this generality by using a dozen different languages for the examples, but I felt that would be too confusing for the reader.
Still, this book is written for programmers in any language.
Outside of the example sections, I’m not making any assumptions about the language.
I expect the reader to absorb my general comments and apply them to the language they are using.
Indeed, I expect readers to take the JavaScript examples and adapt them to their language.
Martin Fowler
@martinfowler
4. Image a company of theatrical players who go out to various events performing plays.
Typically, a customer will request a few plays and the company charges them based on the size of the audience and the kind of play they perform.
There are currently two kinds of plays that the company performs: tragedies and comedies.
As well as providing a bill for the performance, the company gives its customers “volume credits” which they can use for discounts on future
performances—think of it as a customer loyalty mechanism.
The data for their bills also comes in a JSON file:
invoices.json…
[
{
"customer": "BigCo",
"performances": [
{
"playID": "hamlet",
"audience": 55
},
{
"playID": "as-like",
"audience": 35
},
{
"playID": "othello",
"audience": 40
}
]
}
]
The performers store data about their plays in a simple JSON file that looks something like this:
plays.json…
{
"hamlet": {"name": "Hamlet", "type": "tragedy"},
"as-like": {"name": "As You Like It", "type": "comedy"},
"othello": {"name": "Othello", "type": "tragedy"}
}
Martin Fowler
@martinfowler
5. function statement (invoice, plays) {
let totalAmount = 0;
let volumeCredits = 0;
let result = `Statement for ${invoice.customer}n`;
const format = new Intl.NumberFormat("en-US",
{ style: "currency", currency: "USD", minimumFractionDigits: 2 }).format;
for (let perf of invoice.performances) {
const play = plays[perf.playID];
let thisAmount = 0;
switch (play.type) {
case "tragedy":
thisAmount = 40000;
if (perf.audience > 30)
thisAmount += 1000 * (perf.audience - 30);
break;
case "comedy":
thisAmount = 30000;
if (perf.audience > 20)
thisAmount += 10000 + 500 * (perf.audience - 20);
thisAmount += 300 * perf.audience;
break;
default:
throw new Error(`unknown type: ${play.type}`);
}
// add volume credits
volumeCredits += Math.max(perf.audience - 30, 0);
// add extra credit for every ten comedy attendees
if ("comedy" === play.type) volumeCredits += Math.floor(perf.audience / 5);
// print line for this order
result += ` ${play.name}: ${format(thisAmount/100)} (${perf.audience} seats)n`;
totalAmount += thisAmount;
}
result += `Amount owed is ${format(totalAmount/100)}n`;
result += `You earned ${volumeCredits} creditsn`;
return result;
}
The code that prints the bill is this simple function.
What are your thoughts on the design of this program? The first thing I’d say is that it’s tolerable
as it is—a program so short doesn’t require any deep structure to be comprehensible. But
remember my earlier point that I have to keep examples small. Imagine this program on a larger
scale—perhaps hundreds of lines long. At that size, a single inline function is hard to understand.
Given that the program works, isn’t any statement about its structure merely an aesthetic
judgment, a dislike of “ugly” code? After all, the compiler doesn’t care whether the code is ugly
or clean. But when I change the system, there is a human involved, and humans do care. A poorly
designed system is hard to change—because it is difficult to figure out what to change and how
these changes will interact with the existing code to get the behavior I want. And if it is hard to
figure out what to change, there is a good chance that I will make mistakes and introduce bugs.
Thus, if I’m faced with modifying a program with hundreds of lines of code, I’d rather it be
structured into a set of functions and other program elements that allow me to understand more
easily what the program is doing. If the program lacks structure, it’s usually easier for me to add
structure to the program first, and then make the change I need.
Martin Fowler
@martinfowler
6. function statement (invoice, plays) {
let totalAmount = 0;
let volumeCredits = 0;
let result = `Statement for ${invoice.customer}n`;
const format = new Intl.NumberFormat("en-US",
{ style: "currency", currency: "USD", minimumFractionDigits: 2 }).format;
for (let perf of invoice.performances) {
const play = plays[perf.playID];
let thisAmount = 0;
switch (play.type) {
case "tragedy":
thisAmount = 40000;
if (perf.audience > 30)
thisAmount += 1000 * (perf.audience - 30);
break;
case "comedy":
thisAmount = 30000;
if (perf.audience > 20)
thisAmount += 10000 + 500 * (perf.audience - 20);
thisAmount += 300 * perf.audience;
break;
default:
throw new Error(`unknown type: ${play.type}`);
}
// add volume credits
volumeCredits += Math.max(perf.audience - 30, 0);
// add extra credit for every ten comedy attendees
if ("comedy" === play.type) volumeCredits += Math.floor(perf.audience / 5);
// print line for this order
result += ` ${play.name}: ${format(thisAmount/100)} (${perf.audience} seats)n`;
totalAmount += thisAmount;
}
result += `Amount owed is ${format(totalAmount/100)}n`;
result += `You earned ${volumeCredits} creditsn`;
return result;
}
In this case, I have a couple of changes that the users would like to make. First, they want a
statement printed in HTML. Consider what impact this change would have. I’m faced with adding
conditional statements around every statement that adds a string to the result. That will add a host
of complexity to the function. Faced with that, most people prefer to copy the method and change
it to emit HTML. Making a copy may not seem too onerous a task, but it sets up all sorts of
problems for the future. Any changes to the charging logic would force me to update both
methods—and to ensure they are updated consistently. If I’m writing a program that will never
change again, this kind of copy-and-paste is fine. But if it’s a long-lived program, then
duplication is a menace.
This brings me to a second change. The players are looking to perform more kinds of plays: they
hope to add history, pastoral, pastoral-comical, historical-pastoral, tragical-historical, tragical-
comical-historical-pastoral, scene individable, and poem unlimited to their repertoire. They
haven’t exactly decided yet what they want to do and when. This change will affect both the way
their plays are charged for and the way volume credits are calculated. As an experienced
developer I can be sure that whatever scheme they come up with, they will change it again within
six months. After all, when feature requests come, they come not as single spies but in battalions.
Martin Fowler
@martinfowler
7. @philip_schwarz
In this slide deck we are going to
1. Translate Martin Fowler’s initial Javascript program into Java
2. Follow in his refactoring footsteps, transforming our Java program so that it is easier to
understand and easier to change.
On the very few occasions when a decision is made that turns out not to be a good fit in a Java
context, we’ll make an alternative decision that is more suitable for the Java version of the
program.
In the process, we’ll be using the following Java language features incorporated into long-term
support (LTS) version JDK 17 (the previous LTS version being JDK 11):
• Text blocks (JDK 15)
• Records (JDK 16)
• Sealed interfaces (JDK 17)
To keep the pace snappy, we’ll sometimes coalesce a few of Martin’s refactoring nanosteps or
microsteps into one (see next slide for a definition of these two types of refactoring step).
8. https://blog.thecodewhisperer.com/permalink/breaking-through-your-refactoring-rut
Some Helpful Terms
In my lexicon, a nanostep is something like adding a new field to a class. Another nanostep is finding
code that wrote to an existing field and adding code that writes the corresponding value to the new
field, keeping their values synchronized with each other. Yet another is remembering the keystroke for
“extract variable” so that you can simply type the expression (right-hand value) that you have in mind
first, then assign it to a new variable (and let the computer compute the type of the variable for you).
A microstep is a collection of related nanosteps, like introducing an interface and changing a few
classes to implement that interface, adding empty/default method implementations to the classes that
now need it. Another is pushing a value up out of the constructor into its parameter list. Yet another is
remembering that you can either extract a value to a variable before extracting code into a method or
you can extract the method first, then introduce the value as a parameter, and which keystrokes in
NetBeans make that happen.
A move is a collection of related microsteps, like inverting the dependency between A and B, where A
used to invoke B, but now A fires an event which B subscribes to and handles.
J. B. Rainsberger
@jbrains
9. invoices.json…
[
{
"customer": "BigCo",
"performances": [
{
"playID": "hamlet",
"audience": 55
},
{
"playID": "as-like",
"audience": 35
},
{
"playID": "othello",
"audience": 40
}
]
}
]
plays.json…
{
"hamlet": {"name": "Hamlet", "type": "tragedy"},
"as-like": {"name": "As You Like It", "type": "comedy"},
"othello": {"name": "Othello", "type": "tragedy"}
}
record Invoice(String customer, List<Performance> performances) { }
record Performance(String playID, int audience) { }
record Play(String name, String type) { }
static final List<Invoice> invoices =
List.of(
new Invoice(
"BigCo",
List.of(new Performance( "hamlet", 55),
new Performance("as-like", 35),
new Performance("othello", 40))));
static final Map<String,Play> plays = Map.of(
"hamlet" , new Play("Hamlet", "tragedy"),
"as-like", new Play("As You Like It", "comedy"),
"othello", new Play("Othello", "tragedy"));
Let’s knock up some Java data structures
for plays, invoices and performances.
10. static String statement(Invoice invoice, Map<String, Play> plays) {
var totalAmount = 0;
var volumeCredits = 0;
var result = "Statement for " + invoice.customer() + "n";
final var formatter = NumberFormat.getCurrencyInstance(Locale.US);
formatter.setCurrency(Currency.getInstance(Locale.US));
for(Performance perf : invoice.performances()) {
final var play = plays.get(perf.playID());
var thisAmount = 0;
switch (play.type()) {
case "tragedy" -> {
thisAmount = 40_000;
if (perf.audience() > 30)
thisAmount +=1_000 * (perf.audience() - 30);
}
case "comedy" -> {
thisAmount = 30_000;
if (perf.audience() > 20)
thisAmount += 10_000 + 500 * (perf.audience() - 20);
thisAmount += 300 * perf.audience();
}
default ->
throw new IllegalArgumentException("unknown type " + play.type());
}
// add volume credits
volumeCredits += Math.max(perf.audience() - 30, 0);
// add extra credit for every ten comedy attendees
if ("comedy" == play.type())
volumeCredits += Math.floor(perf.audience() / 5);
// print line for this order
result += " " + play.name() + ": " + formatter.format(thisAmount/100)
+ " (" + perf.audience() + " seats)n";
totalAmount += thisAmount;
}
result += "Amount owed is " + formatter.format(totalAmount/100) + "n";
result += "You earned " + volumeCredits + " creditsn";
return result;
}
function statement (invoice, plays) {
let totalAmount = 0;
let volumeCredits = 0;
let result = `Statement for ${invoice.customer}n`;
const format = new Intl.NumberFormat("en-US",
{ style: "currency", currency: "USD", minimumFractionDigits: 2 }).format;
for (let perf of invoice.performances) {
const play = plays[perf.playID];
let thisAmount = 0;
switch (play.type) {
case "tragedy":
thisAmount = 40000;
if (perf.audience > 30)
thisAmount += 1000 * (perf.audience - 30);
break;
case "comedy":
thisAmount = 30000;
if (perf.audience > 20)
thisAmount += 10000 + 500 * (perf.audience - 20);
thisAmount += 300 * perf.audience;
break;
default:
throw new Error(`unknown type: ${play.type}`);
}
// add volume credits
volumeCredits += Math.max(perf.audience - 30, 0);
// add extra credit for every ten comedy attendees
if ("comedy" === play.type) volumeCredits += Math.floor(perf.audience / 5);
// print line for this order
result += ` ${play.name}: ${format(thisAmount/100)} (${perf.audience} seats)n`;
totalAmount += thisAmount;
}
result += `Amount owed is ${format(totalAmount/100)}n`;
result += `You earned ${volumeCredits} creditsn`;
return result;
}
Here is a literal
translation of the
Javascript
program into Java.
11. record Performance(String playID, int audience) { }
record Invoice(String customer, List<Performance> performances) { }
record Play(String name, String type) { }
static final List<Invoice> invoices =
List.of(
new Invoice(
"BigCo",
List.of(new Performance( "hamlet", 55),
new Performance("as-like", 35),
new Performance("othello", 40))));
static final Map<String,Play> plays = Map.of(
"hamlet" , new Play("Hamlet", "tragedy"),
"as-like", new Play("As You Like It", "comedy"),
"othello", new Play("Othello", "tragedy"));
public static void main(String[] args) {
if (!Statement.statement(invoices.get(0), plays).equals(
"""
Statement for BigCo
Hamlet: $650.00 (55 seats)
As You Like It: $580.00 (35 seats)
Othello: $500.00 (40 seats)
Amount owed is $1,730.00
You earned 47 credits
"""
)) throw new AssertionError();
}
Here is the Java code again, together with the data
structures we created earlier, and also a simple
regression test consisting of a single assertion.
static String statement(Invoice invoice, Map<String, Play> plays) {
var totalAmount = 0;
var volumeCredits = 0;
var result = "Statement for " + invoice.customer() + "n";
final var formatter = NumberFormat.getCurrencyInstance(Locale.US);
formatter.setCurrency(Currency.getInstance(Locale.US));
for(Performance perf : invoice.performances()) {
final var play = plays.get(perf.playID());
var thisAmount = 0;
switch (play.type()) {
case "tragedy" -> {
thisAmount = 40_000;
if (perf.audience() > 30)
thisAmount +=1_000 * (perf.audience() - 30);
}
case "comedy" -> {
thisAmount = 30_000;
if (perf.audience() > 20)
thisAmount += 10_000 + 500 * (perf.audience() - 20);
thisAmount += 300 * perf.audience();
}
default ->
throw new IllegalArgumentException("unknown type " + play.type());
}
// add volume credits
volumeCredits += Math.max(perf.audience() - 30, 0);
// add extra credit for every ten comedy attendees
if ("comedy" == play.type())
volumeCredits += Math.floor(perf.audience() / 5);
// print line for this order
result += " " + play.name() + ": " + formatter.format(thisAmount/100)
+ " (" + perf.audience() + " seats)n";
totalAmount += thisAmount;
}
result += "Amount owed is " + formatter.format(totalAmount/100) + "n";
result += "You earned " + volumeCredits + " creditsn";
return result;
}
12. Yes, I hear you! Using mutable variables is best
avoided when it is unnecessary.
We are only using such variables in order to be
faithful to Martin Fowler’s initial Javascript
program.
Don’t worry: as we refactor the code, we’ll slowly
but surely eliminate such mutability.
static String statement(Invoice invoice, Map<String, Play> plays) {
var totalAmount = 0;
var volumeCredits = 0;
var result = "Statement for " + invoice.customer() + "n";
final var formatter = NumberFormat.getCurrencyInstance(Locale.US);
formatter.setCurrency(Currency.getInstance(Locale.US));
for(Performance perf : invoice.performances()) {
final var play = plays.get(perf.playID());
var thisAmount = 0;
switch (play.type()) {
case "tragedy" -> {
thisAmount = 40_000;
if (perf.audience() > 30)
thisAmount += 1_000 * (perf.audience() - 30);
}
case "comedy" -> {
thisAmount = 30_000;
if (perf.audience() > 20)
thisAmount += 10_000 + 500 * (perf.audience() - 20);
thisAmount += 300 * perf.audience();
}
default ->
throw new IllegalArgumentException("unknown type " + play.type());
}
// add volume credits
volumeCredits += Math.max(perf.audience() - 30, 0);
// add extra credit for every ten comedy attendees
if ("comedy" == play.type())
volumeCredits += Math.floor(perf.audience() / 5);
// print line for this order
result += " " + play.name() + ": " + formatter.format(thisAmount/100)
+ " (" + perf.audience() + " seats)n";
totalAmount += thisAmount;
}
result += "Amount owed is " + formatter.format(totalAmount/100) + "n";
result += "You earned " + volumeCredits + " creditsn";
return result;
}
14. When refactoring a long function like this, I
mentally try to identify points that
separate different parts of the overall
behaviour.
The first chunk that leaps to my eye is the
switch statement in the middle.
Martin Fowler
@martinfowler
static String statement(Invoice invoice, Map<String, Play> plays) {
var totalAmount = 0;
var volumeCredits = 0;
var result = "Statement for " + invoice.customer() + "n";
final var formatter = NumberFormat.getCurrencyInstance(Locale.US);
formatter.setCurrency(Currency.getInstance(Locale.US));
for(Performance perf : invoice.performances()) {
final var play = plays.get(perf.playID());
var thisAmount = 0;
switch (play.type()) {
case "tragedy" -> {
thisAmount = 40_000;
if (perf.audience() > 30)
thisAmount +=1_000 * (perf.audience() - 30);
}
case "comedy" -> {
thisAmount = 30_000;
if (perf.audience() > 20)
thisAmount += 10_000 + 500 * (perf.audience() - 20);
thisAmount += 300 * perf.audience();
}
default ->
throw new IllegalArgumentException("unknown type " + play.type());
}
// add volume credits
volumeCredits += Math.max(perf.audience() - 30, 0);
// add extra credit for every ten comedy attendees
if ("comedy" == play.type())
volumeCredits += Math.floor(perf.audience() / 5);
// print line for this order
result += " " + play.name() + ": " + formatter.format(thisAmount/100)
+ " (" + perf.audience() + " seats)n";
totalAmount += thisAmount;
}
result += "Amount owed is " + formatter.format(totalAmount/100) + "n";
result += "You earned " + volumeCredits + " creditsn";
return result;
}
15. static String statement(Invoice invoice, Map<String, Play> plays) {
var totalAmount = 0;
var volumeCredits = 0;
var result = "Statement for " + invoice.customer() + "n";
final var formatter = NumberFormat.getCurrencyInstance(Locale.US);
formatter.setCurrency(Currency.getInstance(Locale.US));
for(Performance perf : invoice.performances()) {
final var play = plays.get(perf.playID());
var thisAmount = 0;
switch (play.type()) {
case "tragedy" -> {
thisAmount = 40_000;
if (perf.audience() > 30)
thisAmount +=1_000 * (perf.audience() - 30);
}
case "comedy" -> {
thisAmount = 30_000;
if (perf.audience() > 20)
thisAmount += 10_000 + 500 * (perf.audience() - 20);
thisAmount += 300 * perf.audience();
}
default ->
throw new IllegalArgumentException("unknown type " + play.type());
}
// add volume credits
volumeCredits += Math.max(perf.audience() - 30, 0);
// add extra credit for every ten comedy attendees
if ("comedy" == play.type())
volumeCredits += Math.floor(perf.audience() / 5);
// print line for this order
result += " " + play.name() + ": " + formatter.format(thisAmount/100)
+ " (" + perf.audience() + " seats)n";
totalAmount += thisAmount;
}
result += "Amount owed is " + formatter.format(totalAmount/100) + "n";
result += "You earned " + volumeCredits + " creditsn";
return result;
}
BiFunction<Performance,Play,Integer> amountFor = (aPerformance, play) -> {
var result = 0;
switch (play.type()) {
case "tragedy" -> {
result = 40_000;
if (aPerformance.audience() > 30)
result += 1_000 * (aPerformance.audience() - 30);
}
case "comedy" -> {
result = 30_000;
if (aPerformance.audience() > 20)
result += 10_000 + 500 * (aPerformance.audience() - 20);
result += 300 * aPerformance.audience();
}
default ->
throw new IllegalArgumentException("unknown type " + play.type());
}
return result;
}
static String statement(Invoice invoice, Map<String, Play> plays) {
var totalAmount = 0;
var volumeCredits = 0;
var result = "Statement for " + invoice.customer() + "n";
final var formatter = NumberFormat.getCurrencyInstance(Locale.US);
formatter.setCurrency(Currency.getInstance(Locale.US));
for(Performance perf : invoice.performances()) {
final var play = plays.get(perf.playID());
final var thisAmount = amountFor.apply(perf, play);
// add volume credits
volumeCredits += Math.max(perf.audience() - 30, 0);
// add extra credit for every ten comedy attendees
if ("comedy" == play.type())
volumeCredits += Math.floor(perf.audience() / 5);
// print line for this order
result += " " + play.name() + ": " + formatter.format(thisAmount/100)
+ " (" + perf.audience() + " seats)n";
totalAmount += thisAmount;
}
result += "Amount owed is " + formatter.format(totalAmount/100) + "n";
result += "You earned " + volumeCredits + " creditsn";
return result;
}
• Extract Function amountFor
• In amountFor function:
• rename perf arg to aPerformance
• rename thisAmount arg to result
16. When Martin Fowler extracts a Javascript function from another, he nests the extracted child
function inside the parent function from which it is extracted.
That makes sense, both to encapsulate (hide) the child function, which is just an implementation
detail of the parent function, and to simplify the signature of a child function needing access to one or
more parameters of the parent function, since nesting the child function means the parent’s
parameters are then directly accessible to it, rather than also having to be passed as parameters to it.
The problem is that in our case, the functions in question are Java methods, but Java does not directly
support nested methods.
Because Martin Fowler’s nesting of child functions is quite instrumental in his chosen refactoring
approach, we are going to strike a compromise and define child functions as lambda functions, so
that we are able to nest them inside their parent function.
The relatively small price that we’ll pay for this compromise is that invoking lambda functions is more
clunky than invoking methods (f.apply(x) rather than f(x)).
However, in the interest of clarity and brevity, I will at times show the statement function without also
showing its child functions.
In the previous slide for example, although the amountFor function was extracted from statement, it
is shown outside statement rather than nested inside it.
In the statement function on the left however, we do see amountFor nested inside statement.
@philip_schwarz
static String statement(Invoice invoice, Map<String, Play> plays) {
BiFunction<Performance,Play,Integer> amountFor = (aPerformance, play) -> {
var result = 0;
switch (play.type()) {
case "tragedy" -> {
result = 40_000;
if (aPerformance.audience() > 30)
result += 1_000 * (aPerformance.audience() - 30);
}
case "comedy" -> {
result = 30_000;
if (aPerformance.audience() > 20)
result += 10_000 + 500 * (aPerformance.audience() - 20);
result += 300 * aPerformance.audience();
}
default ->
throw new IllegalArgumentException("unknown type " + play.type());
}
return result;
};
var totalAmount = 0;
var volumeCredits = 0;
var result = "Statement for " + invoice.customer() + "n";
final var formatter = NumberFormat.getCurrencyInstance(Locale.US);
formatter.setCurrency(Currency.getInstance(Locale.US));
for(Performance perf : invoice.performances()) {
final var play = plays.get(perf.playID());
final var thisAmount = amountFor.apply(perf, play);
// add volume credits
volumeCredits += Math.max(perf.audience() - 30, 0);
// add extra credit for every ten comedy attendees
if ("comedy" == play.type())
volumeCredits += Math.floor(perf.audience() / 5);
// print line for this order
result += " " + play.name() + ": " + formatter.format(thisAmount/100)
+ " (" + perf.audience() + " seats)n";
totalAmount += thisAmount;
}
result += "Amount owed is " + formatter.format(totalAmount/100) + "n";
result += "You earned " + volumeCredits + " creditsn";
return result;
}
17. BiFunction<Performance,Play,Integer> amountFor = (aPerformance, play) -> {
var result = 0;
switch (play.type()) {
case "tragedy" -> {
result = 40_000;
if (aPerformance.audience() > 30)
result += 1_000 * (aPerformance.audience() - 30);
}
case "comedy" -> {
result = 30_000;
if (aPerformance.audience() > 20)
result += 10_000 + 500 * (aPerformance.audience() - 20);
result += 300 * aPerformance.audience();
}
default -> throw new IllegalArgumentException("unknown type " + play.type());
}
return result;
}
Martin Fowler
@martinfowler
The next item to consider for renaming is the play
parameter, but I have a different fate for that.
18. • Decomposing the statement Function
• Removing the play Variable
Martin Fowler
@martinfowler
19. The next two slides perform a Replace Temp with Query refactoring on the play variable.
Such a refactoring is itself composed of the following refactorings:
• Extract Function
• Inline Variable
Removing the play Variable
static String statement(Invoice invoice, Map<String, Play> plays) {
var totalAmount = 0;
var volumeCredits = 0;
var result = "Statement for " + invoice.customer() + "n";
final var formatter = NumberFormat.getCurrencyInstance(Locale.US);
formatter.setCurrency(Currency.getInstance(Locale.US));
for(Performance perf : invoice.performances()) {
final var play = plays.get(perf.playID());
final var thisAmount = amountFor.apply(perf, play);
// add volume credits
volumeCredits += Math.max(perf.audience() - 30, 0);
// add extra credit for every ten comedy attendees
if ("comedy" == play.type())
volumeCredits += Math.floor(perf.audience() / 5);
// print line for this order
result += " " + play.name() + ": " + formatter.format(thisAmount/100)
+ " (" + perf.audience() + " seats)n";
totalAmount += thisAmount;
}
result += "Amount owed is " + formatter.format(totalAmount/100) + "n";
result += "You earned " + volumeCredits + " creditsn";
return result;
}
20. • Extract Function playFor
• rename playFor perf parameter to
aPerformance
Removing the play Variable
static String statement(Invoice invoice, Map<String, Play> plays) {
var totalAmount = 0;
var volumeCredits = 0;
var result = "Statement for " + invoice.customer() + "n";
final var formatter = NumberFormat.getCurrencyInstance(Locale.US);
formatter.setCurrency(Currency.getInstance(Locale.US));
for(Performance perf : invoice.performances()) {
final var play = plays.get(perf.playID());
final var thisAmount = amountFor.apply(perf, play);
// add volume credits
volumeCredits += Math.max(perf.audience() - 30, 0);
// add extra credit for every ten comedy attendees
if ("comedy" == play.type())
volumeCredits += Math.floor(perf.audience() / 5);
// print line for this order
result += " " + play.name() + ": " + formatter.format(thisAmount/100)
+ " (" + perf.audience() + " seats)n";
totalAmount += thisAmount;
}
result += "Amount owed is " + formatter.format(totalAmount/100) + "n";
result += "You earned " + volumeCredits + " creditsn";
return result;
}
static String statement(Invoice invoice, Map<String, Play> plays) {
var totalAmount = 0;
var volumeCredits = 0;
var result = "Statement for " + invoice.customer() + "n";
final var formatter = NumberFormat.getCurrencyInstance(Locale.US);
formatter.setCurrency(Currency.getInstance(Locale.US));
for(Performance perf : invoice.performances()) {
final var play = playFor.apply(perf);
final var thisAmount = amountFor.apply(perf, play);
// add volume credits
volumeCredits += Math.max(perf.audience() - 30, 0);
// add extra credit for every ten comedy attendees
if ("comedy" == play.type())
volumeCredits += Math.floor(perf.audience() / 5);
// print line for this order
result += " " + play.name() + ": " + formatter.format(thisAmount/100)
+ " (" + perf.audience() + " seats)n";
totalAmount += thisAmount;
}
result += "Amount owed is " + formatter.format(totalAmount/100) + "n";
result += "You earned " + volumeCredits + " creditsn";
return result;
}
Function<Performance,Play> playFor = aPerformance ->
plays.get(aPerformance.playID());
21. Inline Variable play in statement function
Removing the play Variable
static String statement(Invoice invoice, Map<String, Play> plays) {
var totalAmount = 0;
var volumeCredits = 0;
var result = "Statement for " + invoice.customer() + "n";
final var formatter = NumberFormat.getCurrencyInstance(Locale.US);
formatter.setCurrency(Currency.getInstance(Locale.US));
for(Performance perf : invoice.performances()) {
final var play = playFor.apply(perf);
final var thisAmount = amountFor.apply(perf, play);
// add volume credits
volumeCredits += Math.max(perf.audience() - 30, 0);
// add extra credit for every ten comedy attendees
if ("comedy" == play.type())
volumeCredits += Math.floor(perf.audience() / 5);
// print line for this order
result += " " + play.name() + ": " + formatter.format(thisAmount/100)
+ " (" + perf.audience() + " seats)n";
totalAmount += thisAmount;
}
result += "Amount owed is " + formatter.format(totalAmount/100) + "n";
result += "You earned " + volumeCredits + " creditsn";
return result;
}
static String statement(Invoice invoice, Map<String, Play> plays) {
var totalAmount = 0;
var volumeCredits = 0;
var result = "Statement for " + invoice.customer() + "n";
final var formatter = NumberFormat.getCurrencyInstance(Locale.US);
formatter.setCurrency(Currency.getInstance(Locale.US));
for(Performance perf : invoice.performances()) {
final var thisAmount = amountFor.apply(perf, playFor.apply(perf));
// add volume credits
volumeCredits += Math.max(perf.audience() - 30, 0);
// add extra credit for every ten comedy attendees
if ("comedy" == playFor.apply(perf).type())
volumeCredits += Math.floor(perf.audience() / 5);
// print line for this order
result += " " + playFor.apply(perf).name() + ": " + formatter.format(thisAmount/100)
+ " (" + perf.audience() + " seats)n";
totalAmount += thisAmount;
}
result += "Amount owed is " + formatter.format(totalAmount/100) + "n";
result += "You earned " + volumeCredits + " creditsn";
return result;
}
22. BiFunction<Performance,Play,Integer> amountFor = (aPerformance, play) -> {
var result = 0;
switch (play.type()) {
case "tragedy" -> {
result = 40_000;
if (aPerformance.audience() > 30)
result += 1_000 * (aPerformance.audience() - 30);
}
case "comedy" -> {
result = 30_000;
if (aPerformance.audience() > 20)
result += 10_000 + 500 * (aPerformance.audience() - 20);
result += 300 * aPerformance.audience();
}
default ->
throw new IllegalArgumentException(
"unknown type " + play.type());
}
return result;
}
in amountFor function: replace references
to play parameter with invocations of
playFor function
Removing the play Variable
BiFunction<Performance,Play,Integer> amountFor = (aPerformance, play) -> {
var result = 0;
switch (playFor.apply(aPerformance).type()) {
case "tragedy" -> {
result = 40_000;
if (aPerformance.audience() > 30)
result += 1_000 * (aPerformance.audience() - 30);
}
case "comedy" -> {
result = 30_000;
if (aPerformance.audience() > 20)
result += 10_000 + 500 * (aPerformance.audience() - 20);
result += 300 * aPerformance.audience();
}
default ->
throw new IllegalArgumentException(
"unknown type " + playFor.apply(aPerformance).type());
}
return result;
}
23. static String statement(Invoice invoice, Map<String, Play> plays) {
var totalAmount = 0;
var volumeCredits = 0;
var result = "Statement for " + invoice.customer() + "n";
final var formatter = NumberFormat.getCurrencyInstance(Locale.US);
formatter.setCurrency(Currency.getInstance(Locale.US));
for(Performance perf : invoice.performances()) {
final var thisAmount = amountFor.apply(perf, playFor.apply(perf));
// add volume credits
volumeCredits += Math.max(perf.audience() - 30, 0);
// add extra credit for every ten comedy attendees
if ("comedy" == playFor.apply(perf).type())
volumeCredits += Math.floor(perf.audience() / 5);
// print line for this order
result += " " + playFor.apply(perf).name() + ": " + formatter.format(thisAmount/100)
+ " (" + perf.audience() + " seats)n";
totalAmount += thisAmount;
}
result += "Amount owed is " + formatter.format(totalAmount/100) + "n";
result += "You earned " + volumeCredits + " creditsn";
return result;
}
static String statement(Invoice invoice, Map<String, Play> plays) {
var totalAmount = 0;
var volumeCredits = 0;
var result = "Statement for " + invoice.customer() + "n";
final var formatter = NumberFormat.getCurrencyInstance(Locale.US);
formatter.setCurrency(Currency.getInstance(Locale.US));
for(Performance perf : invoice.performances()) {
final var thisAmount = amountFor.apply(perf);
// add volume credits
volumeCredits += Math.max(perf.audience() - 30, 0);
// add extra credit for every ten comedy attendees
if ("comedy" == playFor.apply(perf).type())
volumeCredits += Math.floor(perf.audience() / 5);
// print line for this order
result += " " + playFor.apply(perf).name() + ": " + formatter.format(thisAmount/100)
+ " (" + perf.audience() + " seats)n";
totalAmount += thisAmount;
}
result += "Amount owed is " + formatter.format(totalAmount/100) + "n";
result += "You earned " + volumeCredits + " creditsn";
return result;
}
Removing the play Variable
BiFunction<Performance,Play,Integer> amountFor = (aPerformance, play) -> {
var result = 0;
switch (playFor.apply(aPerformance).type()) {
case "tragedy" -> {
result = 40_000;
if (aPerformance.audience() > 30)
result += 1_000 * (aPerformance.audience() - 30);
}
case "comedy" -> {
result = 30_000;
if (aPerformance.audience() > 20)
result += 10_000 + 500 * (aPerformance.audience() - 20);
result += 300 * aPerformance.audience();
}
default ->
throw new IllegalArgumentException(
"unknown type " + playFor.apply(aPerformance).type());
}
return result;
}
Function<Performance,Integer> amountFor = aPerformance -> {
var result = 0;
switch (playFor.apply(aPerformance).type()) {
case "tragedy" -> {
result = 40_000;
if (aPerformance.audience() > 30)
result += 1_000 * (aPerformance.audience() - 30);
}
case "comedy" -> {
result = 30_000;
if (aPerformance.audience() > 20)
result += 10_000 + 500 * (aPerformance.audience() - 20);
result += 300 * aPerformance.audience();
}
default ->
throw new IllegalArgumentException(
"unknown type " + playFor.apply(aPerformance).type());
}
return result;
}
Change Function Declaration of amountFor
by removing play parameter
24. Martin Fowler
@martinfowler
Now that I am done with the arguments to
amountFor, I look back at where it’s called.
static String statement(Invoice invoice, Map<String, Play> plays) {
var totalAmount = 0;
var volumeCredits = 0;
var result = "Statement for " + invoice.customer() + "n";
final var formatter = NumberFormat.getCurrencyInstance(Locale.US);
formatter.setCurrency(Currency.getInstance(Locale.US));
for(Performance perf : invoice.performances()) {
final var thisAmount = amountFor.apply(perf);
// add volume credits
volumeCredits += Math.max(perf.audience() - 30, 0);
// add extra credit for every ten comedy attendees
if ("comedy" == playFor.apply(perf).type())
volumeCredits += Math.floor(perf.audience() / 5);
// print line for this order
result += " " + playFor.apply(perf).name() + ": " + formatter.format(thisAmount/100)
+ " (" + perf.audience() + " seats)n";
totalAmount += thisAmount;
}
result += "Amount owed is " + formatter.format(totalAmount/100) + "n";
result += "You earned " + volumeCredits + " creditsn";
return result;
}
25. static String statement(Invoice invoice, Map<String, Play> plays) {
var totalAmount = 0;
var volumeCredits = 0;
var result = "Statement for " + invoice.customer() + "n";
final var formatter = NumberFormat.getCurrencyInstance(Locale.US);
formatter.setCurrency(Currency.getInstance(Locale.US));
for(Performance perf : invoice.performances()) {
final var thisAmount = amountFor.apply(perf);
// add volume credits
volumeCredits += Math.max(perf.audience() - 30, 0);
// add extra credit for every ten comedy attendees
if ("comedy" == playFor.apply(perf).type())
volumeCredits += Math.floor(perf.audience() / 5);
// print line for this order
result += " " + playFor.apply(perf).name() + ": " + formatter.format(thisAmount/100)
+ " (" + perf.audience() + " seats)n";
totalAmount += thisAmount;
}
result += "Amount owed is " + formatter.format(totalAmount/100) + "n";
result += "You earned " + volumeCredits + " creditsn";
return result;
}
static String statement(Invoice invoice, Map<String, Play> plays) {
var totalAmount = 0;
var volumeCredits = 0;
var result = "Statement for " + invoice.customer() + "n";
final var formatter = NumberFormat.getCurrencyInstance(Locale.US);
formatter.setCurrency(Currency.getInstance(Locale.US));
for(Performance perf : invoice.performances()) {
// add volume credits
volumeCredits += Math.max(perf.audience() - 30, 0);
// add extra credit for every ten comedy attendees
if ("comedy" == playFor.apply(perf).type())
volumeCredits += Math.floor(perf.audience() / 5);
// print line for this order
result += " " + playFor.apply(perf).name() + ": " + formatter.format(amountFor.apply(perf)/100)
+ " (" + perf.audience() + " seats)n";
totalAmount += amountFor.apply(perf);
}
result += "Amount owed is " + formatter.format(totalAmount/100) + "n";
result += "You earned " + volumeCredits + " creditsn";
return result;
}
Inline Variable thisAmount in statement function
27. Martin Fowler
@martinfowler
Extracting Volume Credits
Now I get the benefit from removing the play variable as it makes it
easier to extract the volume credits calculation by removing one of
the locally scoped variables. I still have to deal with the other two.
static String statement(Invoice invoice, Map<String, Play> plays) {
var totalAmount = 0;
var volumeCredits = 0;
var result = "Statement for " + invoice.customer() + "n";
final var formatter = NumberFormat.getCurrencyInstance(Locale.US);
formatter.setCurrency(Currency.getInstance(Locale.US));
for(Performance perf : invoice.performances()) {
// add volume credits
volumeCredits += Math.max(perf.audience() - 30, 0);
// add extra credit for every ten comedy attendees
if ("comedy" == playFor.apply(perf).type())
volumeCredits += Math.floor(perf.audience() / 5);
// print line for this order
result += " " + playFor.apply(perf).name() + ": " + formatter.format(amountFor.apply(perf)/100)
+ " (" + perf.audience() + " seats)n";
totalAmount += amountFor.apply(perf);
}
result += "Amount owed is " + formatter.format(totalAmount/100) + "n";
result += "You earned " + volumeCredits + " creditsn";
return result;
}
28. static String statement(Invoice invoice, Map<String, Play> plays) {
var totalAmount = 0;
var volumeCredits = 0;
var result = "Statement for " + invoice.customer() + "n";
final var formatter = NumberFormat.getCurrencyInstance(Locale.US);
formatter.setCurrency(Currency.getInstance(Locale.US));
for(Performance perf : invoice.performances()) {
// add volume credits
volumeCredits += Math.max(perf.audience() - 30, 0);
// add extra credit for every ten comedy attendees
if ("comedy" == playFor.apply(perf).type())
volumeCredits += Math.floor(perf.audience() / 5);
// print line for this order
result += " " + playFor.apply(perf).name() + ": " + formatter.format(amountFor.apply(perf)/100)
+ " (" + perf.audience() + " seats)n";
totalAmount += amountFor.apply(perf);
}
result += "Amount owed is " + formatter.format(totalAmount/100) + "n";
result += "You earned " + volumeCredits + " creditsn";
return result;
}
• Extract Function volumeCreditsFor
• In volumeCreditsFor function:
• rename perf arg to aPerformance
• rename volumeCredits arg to result
static String statement(Invoice invoice, Map<String, Play> plays) {
var totalAmount = 0;
var volumeCredits = 0;
var result = "Statement for " + invoice.customer() + "n";
final var formatter = NumberFormat.getCurrencyInstance(Locale.US);
formatter.setCurrency(Currency.getInstance(Locale.US));
for(Performance perf : invoice.performances()) {
volumeCredits += volumeCreditsFor.apply(perf);
// print line for this order
result += " " + playFor.apply(perf).name() + ": " + formatter.format(amountFor.apply(perf)/100)
+ " (" + perf.audience() + " seats)n";
totalAmount += amountFor.apply(perf);
}
result += "Amount owed is " + formatter.format(totalAmount/100) + "n";
result += "You earned " + volumeCredits + " creditsn";
return result;
}
Function<Performance,Integer> volumeCreditsFor = aPerformance -> {
var result = 0;
result += Math.max(aPerformance.audience() - 30, 0);
if ("comedy" == playFor.apply(aPerformance).type())
result += Math.floor(aPerformance.audience() / 5);
return result;
};
Extracting Volume Credits
29. Martin Fowler
@martinfowler
static String statement(Invoice invoice, Map<String, Play> plays) {
var totalAmount = 0;
var volumeCredits = 0;
var result = "Statement for " + invoice.customer() + "n";
final var formatter = NumberFormat.getCurrencyInstance(Locale.US);
formatter.setCurrency(Currency.getInstance(Locale.US));
for(Performance perf : invoice.performances()) {
volumeCredits += volumeCreditsFor.apply(perf);
// print line for this order
result += " " + playFor.apply(perf).name() + ": " + formatter.format(amountFor.apply(perf)/100)
+ " (" + perf.audience() + " seats)n";
totalAmount += amountFor.apply(perf);
}
result += "Amount owed is " + formatter.format(totalAmount/100) + "n";
result += "You earned " + volumeCredits + " creditsn";
return result;
}
As I suggested before, temporary variables can be a
problem. They are only useful within their own routine,
and therefore encourage long, complex routines.
My next move, then, is to replace some of them. The
easiest one is formatter.
31. Removing the formatter Variable
static String statement(Invoice invoice, Map<String, Play> plays) {
var totalAmount = 0;
var volumeCredits = 0;
var result = "Statement for " + invoice.customer() + "n";
final var formatter = NumberFormat.getCurrencyInstance(Locale.US);
formatter.setCurrency(Currency.getInstance(Locale.US));
for(Performance perf : invoice.performances()) {
volumeCredits += volumeCreditsFor.apply(perf);
// print line for this order
result += " " + playFor.apply(perf).name() + ": " + formatter.format(amountFor.apply(perf)/100)
+ " (" + perf.audience() + " seats)n";
totalAmount += amountFor.apply(perf);
}
result += "Amount owed is " + formatter.format(totalAmount/100) + "n";
result += "You earned " + volumeCredits + " creditsn";
return result;
}
Function<Integer,String> usd = aNumber -> {
final var formatter = NumberFormat.getCurrencyInstance(Locale.US);
formatter.setCurrency(Currency.getInstance(Locale.US));
return formatter.format(aNumber);
};
static String statement(Invoice invoice, Map<String, Play> plays) {
var totalAmount = 0;
var volumeCredits = 0;
var result = "Statement for " + invoice.customer() + "n";
for(Performance perf : invoice.performances()) {
volumeCredits += volumeCreditsFor.apply(perf);
// print line for this order
result += " " + playFor.apply(perf).name() + ": " + usd.apply(amountFor.apply(perf)/100)
+ " (" + perf.audience() + " seats)n";
totalAmount += amountFor.apply(perf);
}
result += "Amount owed is " + usd.apply(totalAmount/100) + "n";
result += "You earned " + volumeCredits + " creditsn";
return result;
}
• Extract Function format
• Replace references to formatter.format with
invocations of format
• Change Function Declaration of format by
renaming function to usd
32. Martin Fowler
@martinfowler
static String statement(Invoice invoice, Map<String, Play> plays) {
var totalAmount = 0;
var volumeCredits = 0;
var result = "Statement for " + invoice.customer() + "n";
for(Performance perf : invoice.performances()) {
volumeCredits += volumeCreditsFor.apply(perf);
// print line for this order
result += " " + playFor.apply(perf).name() + ": " + usd.apply(amountFor.apply(perf)/100)
+ " (" + perf.audience() + " seats)n";
totalAmount += amountFor.apply(perf);
}
result += "Amount owed is " + usd.apply(totalAmount/100) + "n";
result += "You earned " + volumeCredits + " creditsn";
return result;
}
My next terget variable is volumeCredits. This is a trickier
case, as it’s built up during the iterations of the loop.
33. Martin Fowler
@martinfowler
• Decomposing the statement Function
• Removing the play Variable
• Extracting Volume Credits
• Removing the formatter Variable
• Removing Total Volume Credits
34. static String statement(Invoice invoice, Map<String, Play> plays) {
var totalAmount = 0;
var result = "Statement for " + invoice.customer() + "n";
for(Performance perf : invoice.performances()) {
// print line for this order
result += " " + playFor.apply(perf).name() + ": " + usd.apply(amountFor.apply(perf)/100)
+ " (" + perf.audience() + " seats)n";
totalAmount += amountFor.apply(perf);
}
var volumeCredits = 0;
for(Performance perf : invoice.performances()) {
volumeCredits += volumeCreditsFor.apply(perf);
}
result += "Amount owed is " + usd.apply(totalAmount/100) + "n";
result += "You earned " + volumeCredits + " creditsn";
return result;
}
• Apply Split Loop to the loop on
invoice.performances
• Apply Slide Statements to the statement
initialising variable volumeCredits
Removing Total Volume Credits
static String statement(Invoice invoice, Map<String, Play> plays) {
var totalAmount = 0;
var volumeCredits = 0;
var result = "Statement for " + invoice.customer() + "n";
for(Performance perf : invoice.performances()) {
volumeCredits += volumeCreditsFor.apply(perf);
// print line for this order
result += " " + playFor.apply(perf).name() + ": " + usd.apply(amountFor.apply(perf)/100)
+ " (" + perf.audience() + " seats)n";
totalAmount += amountFor.apply(perf);
}
result += "Amount owed is " + usd.apply(totalAmount/100) + "n";
result += "You earned " + volumeCredits + " creditsn";
return result;
}
35. The next two slides perform a Replace Temp with Query refactoring
on the volumeCredits variable.
As we saw earlier on, such a refactoring is itself composed of the
following refactorings:
• Extract Function
• Inline Variable
static String statement(Invoice invoice, Map<String, Play> plays) {
var totalAmount = 0;
var result = "Statement for " + invoice.customer() + "n";
for(Performance perf : invoice.performances()) {
// print line for this order
result += " " + playFor.apply(perf).name() + ": " + usd.apply(amountFor.apply(perf)/100)
+ " (" + perf.audience() + " seats)n";
totalAmount += amountFor.apply(perf);
}
var volumeCredits = 0;
for(Performance perf : invoice.performances()) {
volumeCredits += volumeCreditsFor.apply(perf);
}
result += "Amount owed is " + usd.apply(totalAmount/100) + "n";
result += "You earned " + volumeCredits + " creditsn";
return result;
}
Removing Total Volume Credits
36. static String statement(Invoice invoice, Map<String, Play> plays) {
var totalAmount = 0;
var result = "Statement for " + invoice.customer() + "n";
for(Performance perf : invoice.performances()) {
// print line for this order
result += " " + playFor.apply(perf).name() + ": " + usd.apply(amountFor.apply(perf)/100)
+ " (" + perf.audience() + " seats)n";
totalAmount += amountFor.apply(perf);
}
var volumeCredits = 0;
for(Performance perf : invoice.performances()) {
volumeCredits += volumeCreditsFor.apply(perf);
}
result += "Amount owed is " + usd.apply(totalAmount/100) + "n";
result += "You earned " + volumeCredits + " creditsn";
return result;
}
• Extract Function totalVolumeCredits
• Inline Variable volumeCredits
static String statement(Invoice invoice, Map<String, Play> plays) {
var totalAmount = 0;
var result = "Statement for " + invoice.customer() + "n";
for(Performance perf : invoice.performances()) {
// print line for this order
result += " " + playFor.apply(perf).name() + ": " + usd.apply(amountFor.apply(perf)/100)
+ " (" + perf.audience() + " seats)n";
totalAmount += amountFor.apply(perf);
}
result += "Amount owed is " + usd.apply(totalAmount/100) + "n";
result += "You earned " + totalVolumeCredits.get() + " creditsn";
return result;
}
Supplier<Integer> totalVolumeCredits = () -> {
var volumeCredits = 0;
for (Performance perf : invoice.performances()) {
volumeCredits += volumeCreditsFor.apply(perf);
}
return volumeCredits;
};
Removing Total Volume Credits
37. Martin Fowler
@martinfowler
I then repeat that sequence
to remove totalAmount.
static String statement(Invoice invoice, Map<String, Play> plays) {
var totalAmount = 0;
var result = "Statement for " + invoice.customer() + "n";
for(Performance perf : invoice.performances()) {
// print line for this order
result += " " + playFor.apply(perf).name() + ": " + usd.apply(amountFor.apply(perf)/100)
+ " (" + perf.audience() + " seats)n";
totalAmount += amountFor.apply(perf);
}
result += "Amount owed is " + usd.apply(totalAmount/100) + "n";
result += "You earned " + totalVolumeCredits.get() + " creditsn";
return result;
}
38. Martin Fowler
@martinfowler
• Decomposing the statement Function
• Removing the play Variable
• Extracting Volume Credits
• Removing the formatter Variable
• Removing Total Volume Credits
• Removing Total Amount
39. Removing Total Amount
• Apply Split Loop to the loop on
invoice.performances
• Apply Slide Statements to the statement
initialising variable totalAmount
static String statement(Invoice invoice, Map<String, Play> plays) {
var totalAmount = 0;
var result = "Statement for " + invoice.customer() + "n";
for(Performance perf : invoice.performances()) {
// print line for this order
result += " " + playFor.apply(perf).name() + ": " + usd.apply(amountFor.apply(perf)/100)
+ " (" + perf.audience() + " seats)n";
totalAmount += amountFor.apply(perf);
}
result += "Amount owed is " + usd.apply(totalAmount/100) + "n";
result += "You earned " + totalVolumeCredits.get() + " creditsn";
return result;
}
static String statement(Invoice invoice, Map<String, Play> plays) {
var result = "Statement for " + invoice.customer() + "n";
for(Performance perf : invoice.performances()) {
result += " " + playFor.apply(perf).name() + ": " + usd.apply(amountFor.apply(perf)/100)
+ " (" + perf.audience() + " seats)n";
}
var totalAmount = 0;
for(Performance perf : invoice.performances()) {
totalAmount += amountFor.apply(perf);
}
result += "Amount owed is " + usd.apply(totalAmount/100) + "n";
result += "You earned " + totalVolumeCredits.get() + " creditsn";
return result;
}
40. • Extract Function appleSauce
• Inline Variable totalAmount
static String statement(Invoice invoice, Map<String, Play> plays) {
var result = "Statement for " + invoice.customer() + "n";
for(Performance perf : invoice.performances()) {
result += " " + playFor.apply(perf).name() + ": " + usd.apply(amountFor.apply(perf)/100)
+ " (" + perf.audience() + " seats)n";
}
var totalAmount = 0;
for(Performance perf : invoice.performances()) {
totalAmount += amountFor.apply(perf);
}
result += "Amount owed is " + usd.apply(totalAmount/100) + "n";
result += "You earned " + totalVolumeCredits.get() + " creditsn";
return result;
}
static String statement(Invoice invoice, Map<String, Play> plays) {
var result = "Statement for " + invoice.customer() + "n";
for(Performance perf : invoice.performances()) {
result += " " + playFor.apply(perf).name() + ": " + usd.apply(amountFor.apply(perf)/100)
+ " (" + perf.audience() + " seats)n";
}
result += "Amount owed is " + usd.apply(appleSauce.get()/100) + "n";
result += "You earned " + totalVolumeCredits.get() + " creditsn";
return result;
}
Supplier<Integer> appleSauce = () -> {
var totalAmount = 0;
for (Performance perf : invoice.performances())
totalAmount += amountFor.apply(perf);
return totalAmount;
};
Removing Total Amount
41. Supplier<Integer> totalVolumeCredits = () -> {
var result = 0;
for (Performance perf : invoice.performances())
result += volumeCreditsFor.apply(perf);
return result;
};
Supplier<Integer> totalAmount = () -> {
var result = 0;
for (Performance perf : invoice.performances())
result += amountFor.apply(perf);
return result;
};
Supplier<Integer> totalVolumeCredits = () -> {
var volumeCredits = 0;
for (Performance perf : invoice.performances())
volumeCredits += volumeCreditsFor.apply(perf);
return volumeCredits;
};
Supplier<Integer> appleSauce = () -> {
var totalAmount = 0;
for (Performance perf : invoice.performances())
totalAmount += amountFor.apply(perf);
return totalAmount;
};
static String statement(Invoice invoice, Map<String, Play> plays) {
var result = "Statement for " + invoice.customer() + "n";
for(Performance perf : invoice.performances()) {
result += " " + playFor.apply(perf).name() + ": " + usd.apply(amountFor.apply(perf)/100)
+ " (" + perf.audience() + " seats)n";
}
result += "Amount owed is " + usd.apply(appleSauce.get()/100) + "n";
result += "You earned " + totalVolumeCredits.get() + " creditsn";
return result;
}
static String statement(Invoice invoice, Map<String, Play> plays) {
var result = "Statement for " + invoice.customer() + "n";
for(Performance perf : invoice.performances()) {
result += " " + playFor.apply(perf).name() + ": " + usd.apply(amountFor.apply(perf)/100)
+ " (" + perf.audience() + " seats)n";
}
result += "Amount owed is " + usd.apply(totalAmount.get()/100) + "n";
result += "You earned " + totalVolumeCredits.get() + " creditsn";
return result;
}
• Change Function Declaration of appleSauce
by renaming function to totalAmount
• Rename Variables volumeCredits and
totalAmount to result
Removing Total Amount
42. Martin Fowler
@martinfowler
• Decomposing the statement Function
• Removing the play Variable
• Extracting Volume Credits
• Removing the formatter Variable
• Removing Total Volume Credits
• Removing Total Amount
• Status: Lots of Nested Functions
43. Martin Fowler
@martinfowler
Now is a good time to pause and take a look at the overall state of the code.
The structure of the code is much better now.
The top-level statement function is now just six lines of code, and all it does
is laying out the printing of the statement.
All the calculation logic has been moved out to a handful of supporting
functions.
This makes it easier to understand each individual calculation as well as the
overall flow of the report.
Status: Lots of Nested Functions
static String statement(Invoice invoice, Map<String, Play> plays) {
Function<Performance,Play> playFor = aPerformance -> plays.get(aPerformance.playID());
Function<Performance,Integer> amountFor = aPerformance -> {
var result = 0;
switch (playFor.apply(aPerformance).type()) {
case "tragedy" -> {
result = 40_000;
if (aPerformance.audience() > 30) result += 1_000 * (aPerformance.audience() - 30);
}
case "comedy" -> {
result = 30_000;
if (aPerformance.audience() > 20) result += 10_000 + 500 * (aPerformance.audience() - 20);
result += 300 * aPerformance.audience();
}
default ->
throw new IllegalArgumentException("unknown type " + playFor.apply(aPerformance).type());
}
return result;
};
Function<Performance,Integer> volumeCreditsFor = aPerformance -> {
var result = 0;
result += Math.max(aPerformance.audience() - 30, 0);
if ("comedy" == playFor.apply(aPerformance).type()) result += Math.floor(aPerformance.audience() / 5);
return result;
};
Function<Integer,String> usd = aNumber -> {
final var formatter = NumberFormat.getCurrencyInstance(Locale.US);
formatter.setCurrency(Currency.getInstance(Locale.US));
return formatter.format(aNumber);
};
Supplier<Integer> totalVolumeCredits = () -> {
var result = 0;
for (Performance perf : invoice.performances())
result += volumeCreditsFor.apply(perf);
return result;
};
Supplier<Integer> totalAmount = () -> {
var result = 0;
for (Performance perf : invoice.performances())
result += amountFor.apply(perf);
return result;
};
var result = "Statement for " + invoice.customer() + "n";
for(Performance perf : invoice.performances())
result += " " + playFor.apply(perf).name() + ": " + usd.apply(amountFor.apply(perf)/100)
+ " (" + perf.audience() + " seats)n";
result += "Amount owed is " + usd.apply(totalAmount.get()/100) + "n";
result += "You earned " + totalVolumeCredits.get() + " creditsn";
return result;
}
44. Original Program Refactored Program
static String statement(Invoice invoice, Map<String, Play> plays) {
Function<Performance,Play> playFor = aPerformance -> plays.get(aPerformance.playID());
Function<Performance,Integer> amountFor = aPerformance -> {
var result = 0;
switch (playFor.apply(aPerformance).type()) {
case "tragedy" -> {
result = 40_000;
if (aPerformance.audience() > 30) result += 1_000 * (aPerformance.audience() - 30);
}
case "comedy" -> {
result = 30_000;
if (aPerformance.audience() > 20) result += 10_000 + 500 * (aPerformance.audience() - 20);
result += 300 * aPerformance.audience();
}
default -> throw new IllegalArgumentException("unknown type " + playFor.apply(aPerformance).type());
}
return result;
};
Function<Performance,Integer> volumeCreditsFor = aPerformance -> {
var result = 0;
result += Math.max(aPerformance.audience() - 30, 0);
if ("comedy" == playFor.apply(aPerformance).type()) result += Math.floor(aPerformance.audience() / 5);
return result;
};
Function<Integer,String> usd = aNumber -> {
final var formatter = NumberFormat.getCurrencyInstance(Locale.US);
formatter.setCurrency(Currency.getInstance(Locale.US));
return formatter.format(aNumber);
};
Supplier<Integer> totalVolumeCredits = () -> {
var result = 0;
for (Performance perf : invoice.performances())
result += volumeCreditsFor.apply(perf);
return result;
};
Supplier<Integer> totalAmount = () -> {
var result = 0;
for (Performance perf : invoice.performances())
result += amountFor.apply(perf);
return result;
};
var result = "Statement for " + invoice.customer() + "n";
for(Performance perf : invoice.performances())
result += " " + playFor.apply(perf).name() + ": " + usd.apply(amountFor.apply(perf)/100)
+ " (" + perf.audience() + " seats)n";
result += "Amount owed is " + usd.apply(totalAmount.get()/100) + "n";
result += "You earned " + totalVolumeCredits.get() + " creditsn";
return result;
}
static String statement(Invoice invoice, Map<String, Play> plays) {
var totalAmount = 0;
var volumeCredits = 0;
var result = "Statement for " + invoice.customer() + "n";
final var formatter = NumberFormat.getCurrencyInstance(Locale.US);
formatter.setCurrency(Currency.getInstance(Locale.US));
for(Performance perf : invoice.performances()) {
final var play = plays.get(perf.playID());
var thisAmount = 0;
switch (play.type()) {
case "tragedy" -> {
thisAmount = 40_000;
if (perf.audience() > 30) thisAmount +=1_000 * (perf.audience() - 30);
}
case "comedy" -> {
thisAmount = 30_000;
if (perf.audience() > 20) thisAmount += 10_000 + 500 * (perf.audience() - 20);
thisAmount += 300 * perf.audience();
}
default -> throw new IllegalArgumentException("unknown type " + play.type());
}
// add volume credits
volumeCredits += Math.max(perf.audience() - 30, 0);
// add extra credit for every ten comedy attendees
if ("comedy" == play.type()) volumeCredits += Math.floor(perf.audience() / 5);
// print line for this order
result += " " + play.name() + ": " + formatter.format(thisAmount/100)
+ " (" + perf.audience() + " seats)n";
totalAmount += thisAmount;
}
result += "Amount owed is " + formatter.format(totalAmount/100) + "n";
result += "You earned " + volumeCredits + " creditsn";
return result;
}
45. Martin Fowler
@martinfowler
• Decomposing the statement Function
• Removing the play Variable
• Extracting Volume Credits
• Removing the formatter Variable
• Removing Total Volume Credits
• Removing Total Amount
• Status: Lots of Nested Functions
• Splitting the Phases of Calculation and Formatting
46. Martin Fowler
@martinfowler
So far, my refactoring has focused on adding enough structure to the function
so that I can understand it and see it in terms of its logical parts.
This is often the case early in refactoring. Breaking down complicated chunks
into small pieces is important, as is naming things well.
Now, I can begin to focus more on the functionality change I want to make—
specifically, providing an HTML version of this statement.
In many ways, it’s now much easier to do. With all the calculation code split
out, all I have to do is write an HTML version of the six lines of code at the
bottom.
The problem is that these broken-out functions are nested within the textual
statement method, and I don’t want to copy and paste them into a new
function, however well organized.
Splitting the Phases of Calculation and Formatting
static String statement(Invoice invoice, Map<String, Play> plays) {
Function<Performance,Play> playFor = aPerformance -> plays.get(aPerformance.playID());
Function<Performance,Integer> amountFor = aPerformance -> {
var result = 0;
switch (playFor.apply(aPerformance).type()) {
case "tragedy" -> {
result = 40_000;
if (aPerformance.audience() > 30) result += 1_000 * (aPerformance.audience() - 30);
}
case "comedy" -> {
result = 30_000;
if (aPerformance.audience() > 20) result += 10_000 + 500 * (aPerformance.audience() - 20);
result += 300 * aPerformance.audience();
}
default -> throw new IllegalArgumentException("unknown type " + playFor.apply(aPerformance).type());
}
return result;
};
Function<Performance,Integer> volumeCreditsFor = aPerformance -> {
var result = 0;
result += Math.max(aPerformance.audience() - 30, 0);
if ("comedy" == playFor.apply(aPerformance).type()) result += Math.floor(aPerformance.audience() / 5);
return result;
};
Function<Integer,String> usd = aNumber -> {
final var formatter = NumberFormat.getCurrencyInstance(Locale.US);
formatter.setCurrency(Currency.getInstance(Locale.US));
return formatter.format(aNumber);
};
Supplier<Integer> totalVolumeCredits = () -> {
var result = 0;
for (Performance perf : invoice.performances())
result += volumeCreditsFor.apply(perf);
return result;
};
Supplier<Integer> totalAmount = () -> {
var result = 0;
for (Performance perf : invoice.performances())
result += amountFor.apply(perf);
return result;
};
var result = "Statement for " + invoice.customer() + "n";
for(Performance perf : invoice.performances())
result += " " + playFor.apply(perf).name() + ": " + usd.apply(amountFor.apply(perf)/100)
+ " (" + perf.audience() + " seats)n";
result += "Amount owed is " + usd.apply(totalAmount.get()/100) + "n";
result += "You earned " + totalVolumeCredits.get() + " creditsn";
return result;
}
47. Martin Fowler
@martinfowler
I want the same calculation functions to be used by the text and HTML versions
of the statement.
There are various ways to do this, but one of my favorite techniques is Split
Phase.
My aim here is to divide the logic into two parts: one that calculates the data
required for the statement, the other that renders it into text or HTML.
The first phase creates an intermediate data structure that it passes to the
second.
I start a Split Phase by applying Extract Function to the code that makes up the
second phase.
In this case, that’s the statement printing code, which is in fact the entire
content of statement.
This, together with all the nested functions, goes into its own top-level function
which I call renderPlainText (see next slide).
Splitting the Phases of Calculation and Formatting
static String statement(Invoice invoice, Map<String, Play> plays) {
Function<Performance,Play> playFor = aPerformance -> plays.get(aPerformance.playID());
Function<Performance,Integer> amountFor = aPerformance -> {
var result = 0;
switch (playFor.apply(aPerformance).type()) {
case "tragedy" -> {
result = 40_000;
if (aPerformance.audience() > 30) result += 1_000 * (aPerformance.audience() - 30);
}
case "comedy" -> {
result = 30_000;
if (aPerformance.audience() > 20) result += 10_000 + 500 * (aPerformance.audience() - 20);
result += 300 * aPerformance.audience();
}
default -> throw new IllegalArgumentException("unknown type " + playFor.apply(aPerformance).type());
}
return result;
};
Function<Performance,Integer> volumeCreditsFor = aPerformance -> {
var result = 0;
result += Math.max(aPerformance.audience() - 30, 0);
if ("comedy" == playFor.apply(aPerformance).type()) result += Math.floor(aPerformance.audience() / 5);
return result;
};
Function<Integer,String> usd = aNumber -> {
final var formatter = NumberFormat.getCurrencyInstance(Locale.US);
formatter.setCurrency(Currency.getInstance(Locale.US));
return formatter.format(aNumber);
};
Supplier<Integer> totalVolumeCredits = () -> {
var result = 0;
for (Performance perf : invoice.performances())
result += volumeCreditsFor.apply(perf);
return result;
};
Supplier<Integer> totalAmount = () -> {
var result = 0;
for (Performance perf : invoice.performances())
result += amountFor.apply(perf);
return result;
};
var result = "Statement for " + invoice.customer() + "n";
for(Performance perf : invoice.performances())
result += " " + playFor.apply(perf).name() + ": " + usd.apply(amountFor.apply(perf)/100)
+ " (" + perf.audience() + " seats)n";
result += "Amount owed is " + usd.apply(totalAmount.get()/100) + "n";
result += "You earned " + totalVolumeCredits.get() + " creditsn";
return result;
}
48. static String statement(Invoice invoice, Map<String, Play> plays) {
Function<Performance,Play> playFor = perf -> aPerformance.get(aPerformance.playID());
Function<Performance,Integer> amountFor = aPerformance -> {
var result = 0;
switch (playFor.apply(aPerformance).type()) {
case "tragedy" -> {
result = 40_000;
if (aPerformance.audience() > 30) result += 1_000 * (aPerformance.audience() - 30);
}
case "comedy" -> {
result = 30_000;
if (aPerformance.audience() > 20) result += 10_000 + 500 * (aPerformance.audience() - 20);
result += 300 * aPerformance.audience();
}
default ->
throw new IllegalArgumentException("unknown type " + playFor.apply(aPerformance).type());
}
return result;
};
Function<Performance,Integer> volumeCreditsFor = aPerformance -> {
var result = 0;
result += Math.max(aPerformance.audience() - 30, 0);
if ("comedy" == playFor.apply(aPerformance).type())
result += Math.floor(aPerformance.audience() / 5);
return result;
};
Function<Integer,String> usd = aNumber -> {
final var formatter = NumberFormat.getCurrencyInstance(Locale.US);
formatter.setCurrency(Currency.getInstance(Locale.US));
return formatter.format(aNumber);
};
Supplier<Integer> totalVolumeCredits = () -> {
var result = 0;
for (Performance perf : invoice.performances())
result += volumeCreditsFor.apply(perf);
return result;
};
Supplier<Integer> totalAmount = () -> {
var result = 0;
for (Performance perf : invoice.performances())
result += amountFor.apply(perf);
return result;
};
var result = "Statement for " + invoice.customer() + "n";
for(Performance perf : invoice.performances())
result += " " + playFor.apply(perf).name() + ": " + usd.apply(amountFor.apply(perf)/100)
+ " (" + perf.audience() + " seats)n";
result += "Amount owed is " + usd.apply(totalAmount.get()/100) + "n";
result += "You earned " + totalVolumeCredits.get() + " creditsn";
return result;
}
Extract Function renderPlainText
static String statement(Invoice invoice, Map<String, Play> plays) {
return renderPlainText(invoice, plays);
}
static String renderPlainText(Invoice invoice, Map<String, Play> plays) {
Function<Performance,Play> playFor = aPerformance -> plays.get(aPerformance.playID());
BiFunction<Performance,Play,Integer> amountFor = aPerformance -> {
var result = 0;
switch (playFor.apply(aPerformance).type()) {
case "tragedy" -> {
result = 40_000;
if (aPerformance.audience() > 30) result += 1_000 * (aPerformance.audience() - 30);
}
case "comedy" -> {
result = 30_000;
if (aPerformance.audience() > 20) result += 10_000 + 500 * (aPerformance.audience() - 20);
result += 300 * aPerformance.audience();
}
default ->
throw new IllegalArgumentException("unknown type " + playFor.apply(aPerformance).type());
}
return result;
};
Function<Performance,Integer> volumeCreditsFor = aPerformance -> {
var result = 0;
result += Math.max(aPerformance.audience() - 30, 0);
if ("comedy" == playFor.apply(aPerformance).type())
result += Math.floor(aPerformance.audience() / 5);
return result;
};
Function<Integer,String> usd = aNumber -> {
final var formatter = NumberFormat.getCurrencyInstance(Locale.US);
formatter.setCurrency(Currency.getInstance(Locale.US));
return formatter.format(aNumber);
};
Supplier<Integer> totalVolumeCredits = () -> {
var result = 0;
for (Performance perf : invoice.performances())
result += volumeCreditsFor.apply(perf);
return result;
};
Supplier<Integer> totalAmount = () -> {
var result = 0;
for (Performance perf : invoice.performances())
result += amountFor.apply(perf);
return result;
};
var result = "Statement for " + invoice.customer() + "n";
for(Performance perf : invoice.performances())
result += " " + playFor.apply(perf).name() + ": " + usd.apply(amountFor.apply(perf)/100)
+ " (" + perf.audience() + " seats)n";
result += "Amount owed is " + usd.apply(totalAmount.get()/100) + "n";
result += "You earned " + totalVolumeCredits.get() + " creditsn";
return result;
}
49. In upcoming slides, Martin Fowler will be using the concept of a
Javascript Object, which he creates and then adds fields to:
const foo = {};
foo.bar = abc;
foo.baz = def
What we’ll be doing instead in Java is introduce a record:
record Foo(Bar bar, Baz baz)
50. Martin Fowler
@martinfowler
I do my usual compile-test-commit, then create an object that will act
as my intermediate data structure between the two phases. I pass this
data object in as an argument to renderPlainText.
static String statement(Invoice invoice, Map<String, Play> plays) {
return renderPlainText(invoice, plays);
}
static String renderPlainText(Invoice invoice, Map<String, Play> plays) {
var result = "Statement for " + invoice.customer() + "n";
for(Performance perf : invoice.performances())
result += " " + playFor.apply(perf).name() + ": " + usd.apply(amountFor.apply(perf)/100)
+ " (" + perf.audience() + " seats)n";
result += "Amount owed is " + usd.apply(totalAmount.get()/100) + "n";
result += "You earned " + totalVolumeCredits.get() + " creditsn";
return result;
}
static String statement(Invoice invoice, Map<String, Play> plays) {
final var statementData = new StatementData();
return renderPlainText(statementData, invoice, plays);
}
static String renderPlainText(StatementData data, Invoice invoice, Map<String, Play> plays) {
var result = "Statement for " + invoice.customer() + "n";
for(Performance perf : invoice.performances())
result += " " + playFor.apply(perf).name() + ": " + usd.apply(amountFor.apply(perf)/100)
+ " (" + perf.audience() + " seats)n";
result += "Amount owed is " + usd.apply(totalAmount.get()/100) + "n";
result += "You earned " + totalVolumeCredits.get() + " creditsn";
return result;
}
record StatementData() { }
Splitting the Phases of Calculation and Formatting
51. Martin Fowler
@martinfowler
I now examine the other arguments used by renderPlainText. I want to move the data that comes from them into the intermediate data structure, so
that all the calculation code moves into the statement function and renderPlainText operates solely on data passed to it through the data parameter.
My first move is to take the customer and add it to the intermediate object.
Splitting the Phases of Calculation and Formatting
static String statement(Invoice invoice, Map<String, Play> plays) {
final var statementData = new StatementData(invoice .customer());
return renderPlainText(statementData, invoice, plays);
}
static String renderPlainText(StatementData data, Invoice invoice, Map<String, Play> plays) {
var result = "Statement for " + data.customer() + "n";
for(Performance perf : invoice.performances())
result += " " + playFor.apply(perf).name() + ": " + usd.apply(amountFor.apply(perf)/100)
+ " (" + perf.audience() + " seats)n";
result += "Amount owed is " + usd.apply(totalAmount.get()/100) + "n";
result += "You earned " + totalVolumeCredits.get() + " creditsn";
return result;
}
record StatementData(String customer) { }
static String statement(Invoice invoice, Map<String, Play> plays) {
final var statementData = new StatementData();
return renderPlainText(statementData, invoice, plays);
}
static String renderPlainText(StatementData data, Invoice invoice, Map<String, Play> plays) {
var result = "Statement for " + invoice.customer() + "n";
for(Performance perf : invoice.performances())
result += " " + playFor.apply(perf).name() + ": " + usd.apply(amountFor.apply(perf)/100)
+ " (" + perf.audience() + " seats)n";
result += "Amount owed is " + usd.apply(totalAmount.get()/100) + "n";
result += "You earned " + totalVolumeCredits.get() + " creditsn";
return result;
} record StatementData() { }
52. static String statement(Invoice invoice, Map<String, Play> plays) {
final var statementData = new StatementData(invoice.customer());
return renderPlainText(statementData, invoice, plays);
}
static String renderPlainText(StatementData data, Invoice invoice, Map<String, Play> plays) {
var result = "Statement for " + data.customer() + "n";
for(Performance perf : invoice.performances())
result += " " + playFor.apply(perf).name() + ": " + usd.apply(amountFor.apply(perf)/100)
+ " (" + perf.audience() + " seats)n";
result += "Amount owed is " + usd.apply(totalAmount.get()/100) + "n";
result += "You earned " + totalVolumeCredits.get() + " creditsn";
return result;
}
record StatementData(String customer) { }
Similarly, I add the performances, which allows me
to delete the invoice parameter to renderPlainText.
Martin Fowler
@martinfowler
Splitting the Phases of Calculation and Formatting
static String statement(Invoice invoice, Map<String, Play> plays) {
final var statementData = new StatementData(invoice .customer(), invoice.performances());
return renderPlainText(statementData, plays);
}
static String renderPlainText(StatementData data, Map<String, Play> plays) {
var result = "Statement for " + data.customer() + "n";
for(Performance perf : data.performances())
result += " " + playFor.apply(perf).name() + ": " + usd.apply(amountFor.apply(perf)/100)
+ " (" + perf.audience() + " seats)n";
result += "Amount owed is " + usd.apply(totalAmount.get()/100) + "n";
result += "You earned " + totalVolumeCredits.get() + " creditsn";
return result;
}
record StatementData(String customer, List<Performance> performances) { }
Supplier<Integer> totalAmount = () -> {
var result = 0;
for (Performance perf : invoice.performances())
result += amountFor.apply(perf);
return result;
};
Supplier<Integer> totalVolumeCredits = () -> {
var result = 0;
for (Performance perf : invoice.performances())
result += volumeCreditsFor.apply(perf);
return result;
};
Supplier<Integer> totalAmount = () -> {
var result = 0;
for (Performance perf : data.performances())
result += amountFor.apply(perf);
return result;
};
Supplier<Integer> totalVolumeCredits = () -> {
var result = 0;
for (Performance perf : data.performances())
result += volumeCreditsFor.apply(perf);
return result;
};
53. In upcoming slides, Martin Fowler introduces the notion of ‘enriching’
Performance objects (during the calculation phase) with additional fields
(that are to be used during the formatting phase).
Whilst in Java we’ll ultimately aim to have both a Performance record and an
EnrichedPerformance record, we’ll have to start off by ‘enriching’ the
Performance record with optional fields, and only later remove the optional
fields in favour of a new EnrichedPerformance record.
@philip_schwarz
54. record Performance(String playID, int audience) { }
static String statement(Invoice invoice, Map<String, Play> plays) {
Function<Performance,Play> playFor = aPerformance -> plays.get(aPerformance.playID());
Function<Performance,Performance> enrichPerformance = aPerformance ->
new Performance(aPerformance.playID(),
Optional.of(playFor.apply(aPerformance)),
aPerformance.audience());
final var statementData = new StatementData(
invoice .customer(),
invoice.performances().stream().map(enrichPerformance).collect(toList()));
return renderPlainText(statementData, invoice, plays);
}
static String renderPlainText(StatementData data, Map<String, Play> plays) {
Function<Performance,Integer> amountFor = aPerformance -> {
var result = 0;
switch (aPerformance.play().get().type()) {
case "tragedy" -> {
result = 40_000;
if (aPerformance.audience() > 30)
result += 1_000 * (aPerformance.audience() - 30); }
case "comedy" -> {
result = 30_000;
if (aPerformance.audience() > 20)
result += 10_000 + 500 * (aPerformance.audience() - 20);
result += 300 * aPerformance.audience(); }
default -> throw new IllegalArgumentException(
"unknown type " + aPerformance.play().get().type()); }
return result;
};
Function<Performance,Integer> volumeCreditsFor = aPerformance -> {
var result = 0;
result += Math.max(aPerformance.audience() - 30, 0);
if ("comedy" == aPerformance.play().get().type())
result += Math.floor(aPerformance.audience() / 5);
return result;
};
var result = "Statement for " + data.customer() + "n";
for(Performance perf : data.performances())
result += " "+ perf.play().get().name()+": "+usd.apply(amountFor.apply(perf)/100)
+ " (" + perf.audience() + " seats)n";
result += "Amount owed is " + usd.apply(totalAmount.get()/100) + "n";
result += "You earned " + totalVolumeCredits.get() + " creditsn";
return result;
}
static String statement(Invoice invoice, Map<String, Play> plays) {
final var statementData = new StatementData(invoice.customer(), invoice.performances());
return renderPlainText(statementData, plays);
}
static String renderPlainText(StatementData data, Map<String, Play> plays) {
Function<Performance,Play> playFor = perf -> plays.get(perf.playID());
Function<Performance,Integer> amountFor = aPerformance -> {
var result = 0;
switch (playFor.apply(aPerformance).type()) {
case "tragedy" -> {
result = 40_000;
if (aPerformance.audience() > 30)
result += 1_000 * (aPerformance.audience() - 30); }
case "comedy" -> {
result = 30_000;
if (aPerformance.audience() > 20)
result += 10_000 + 500 * (aPerformance.audience() - 20);
result += 300 * aPerformance.audience(); }
default -> throw new IllegalArgumentException(
"unknown type " + playFor.apply(aPerformance).type()); }
return result;
};
Function<Performance,Integer> volumeCreditsFor = aPerformance -> {
var result = 0;
result += Math.max(aPerformance.audience() - 30, 0);
if ("comedy" == playFor.apply(aPerformance).type())
result += Math.floor(aPerformance.audience() / 5);
return result;
};
var result = "Statement for " + data.customer() + "n";
for(Performance perf : data.performances())
result += " "+playFor.apply(perf).name()+": "+usd.apply(amountFor.apply(perf)/100)
+ " (" + perf.audience() + " seats)n";
result += "Amount owed is " + usd.apply(totalAmount.get()/100) + "n";
result += "You earned " + totalVolumeCredits.get() + " creditsn";
return result;
}
Martin Fowler @martinfowler
Now I’d like the play name to come from the
intermediate data. To do this, I need to enrich the
performance record with data from the play.
record Performance(String playID, Optional<Play> play, int audience) {
Performance(String playID, int audience) { this(playID, Optional.empty(), audience); }}
55. static String statement(Invoice invoice, Map<String, Play> plays) {
Function<Performance,Play> playFor = aPerformance -> plays.get(aPerformance.playID());
Function<Performance,Performance> enrichPerformance = aPerformance ->
new Performance(aPerformance.playID(),
Optional.of(playFor.apply(aPerformance)),
aPerformance.audience());
final var statementData = new StatementData(
invoice .customer(),
invoice.performances().stream().map(enrichPerformance).collect(toList()));
return renderPlainText(statementData, plays);
}
static String renderPlainText(StatementData data, Map<String, Play> plays) {
Function<Performance,Integer> amountFor = aPerformance -> {
var result = 0;
switch (aPerformance.play().get().type()) {
case "tragedy" -> {
result = 40_000;
if (aPerformance.audience() > 30)
result += 1_000 * (aPerformance.audience() - 30); }
case "comedy" -> {
result = 30_000;
if (aPerformance.audience() > 20)
result += 10_000 + 500 * (aPerformance.audience() - 20);
result += 300 * aPerformance.audience(); }
default -> throw new IllegalArgumentException(
"unknown type " + aPerformance.play().get().type()); }
return result;
};
Supplier<Integer> totalAmount = () -> {
var result = 0;
for (Performance perf : data.performances())
result += amountFor.apply(perf);
return result;
};
var result = "Statement for " + data.customer() + "n";
for(Performance perf : data.performances())
result += " "+ perf.play().get().name()+": "+usd.apply(amountFor.apply(perf)/100)
+ " (" + perf.audience() + " seats)n";
result += "Amount owed is " + usd.apply(totalAmount.get()/100) + "n";
result += "You earned " + totalVolumeCredits.get() + " creditsn";
return result;
}
static String statement(Invoice invoice, Map<String, Play> plays) {
Function<Performance,Play> playFor = aPerformance -> plays.get(aPerformance.playID());
Function<Performance,Integer> amountFor = aPerformance -> {
var result = 0;
switch (playFor.apply(aPerformance).type()) {
case "tragedy" -> {
result = 40_000;
if (aPerformance.audience() > 30)
result += 1_000 * (aPerformance.audience() - 30); }
case "comedy" -> {
result = 30_000;
if (aPerformance.audience() > 20)
result += 10_000 + 500 * (aPerformance.audience() - 20);
result += 300 * aPerformance.audience(); }
default -> throw new IllegalArgumentException(
"unknown type " + playFor.apply(aPerformance).type()); }
return result;
};
Function<Performance,Performance> enrichPerformance = aPerformance ->
new Performance(aPerformance.playID(),
Optional.of(playFor.apply(aPerformance)),
aPerformance.audience(),
Optional.of(amountFor.apply(aPerformance)));
final var statementData = new StatementData(
invoice .customer(),
invoice.performances().stream().map(enrichPerformance).collect(toList()));
return renderPlainText(statementData);
}
static String renderPlainText(StatementData data) {
Supplier<Integer> totalAmount = () -> {
var result = 0;
for (Performance perf : data.performances())
result += perf.amount().get();
return result;
};
var result = "Statement for " + data.customer() + "n";
for(Performance perf : data.performances())
result += " "+ perf.play().get().name()+": "+usd.apply(perf.amount().get()/100)
+ " (" + perf.audience() + " seats)n";
result += "Amount owed is " + usd.apply(totalAmount.get()/100) + "n";
result += "You earned " + totalVolumeCredits.get() + " creditsn";
return result;
}
record Performance(
String playID, Optional<Play> play, int audience
){ Performance(String playID, int audience) {
this(playID, Optional.empty(), audience); }}
Martin Fowler
record Performance(
String playID, Optional<Play> play, int audience, Optional<Integer> amount
){ Performance(String playID, int audience) {
this(playID, Optional.empty(), audience, Optional.empty()); }}
I then move amountFor
in a similar way.
56. Note that, on the previous slide, I have already removed the
plays parameter of renderPlainText, since it is no longer used.
In the book, this doesn’t happen till later in this section.
57. static String statement(Invoice invoice, Map<String, Play> plays) {
Function<Performance,Performance> enrichPerformance = aPerformance ->
new Performance(aPerformance.playID(),
Optional.of(playFor.apply(aPerformance)),
aPerformance.audience(),
Optional.of(amountFor.apply(aPerformance)));
final var statementData = new StatementData(
invoice .customer(),
invoice.performances().stream().map(enrichPerformance).collect(toList()));
return renderPlainText(statementData);
}
static String renderPlainText(StatementData data) {
Function<Performance,Integer> volumeCreditsFor = aPerformance -> {
var result = 0;
result += Math.max(aPerformance.audience() - 30, 0);
if ("comedy" == aPerformance.play().get().type())
result += Math.floor(aPerformance.audience() / 5);
return result;
};
Supplier<Integer> totalVolumeCredits = () -> {
var result = 0;
for (Performance perf : data.performances())
result += volumeCreditsFor.apply(perf);
return result;
};
var result = "Statement for " + data.customer() + "n";
for(Performance perf : data.performances())
result += " "+ perf.play().get().name()+": "+usd.apply(perf.amount().get()/100)
+ " (" + perf.audience() + " seats)n";
result += "Amount owed is " + usd.apply(totalAmount.get()/100) + "n";
result += "You earned " + totalVolumeCredits.get() + " creditsn";
return result;
}
Martin Fowler
@martinfowler
Next, I move the
volumeCreditsFor
calculation.
Splitting the Phases of Calculation and Formatting
record Performance(
String playID, Optional<Play> play, int audience,
Optional<Integer> amount
){ Performance(String playID, int audience) {
this(playID, Optional.empty(), audience, Optional.empty()); }}
static String statement(Invoice invoice, Map<String, Play> plays) {
Function<Performance,Integer> volumeCreditsFor = aPerformance -> {
var result = 0;
result += Math.max(aPerformance.audience() - 30, 0);
if ("comedy" == playFor.apply(aPerformance).type())
result += Math.floor(aPerformance.audience() / 5);
return result;
};
Function<Performance,Performance> enrichPerformance = aPerformance ->
new Performance(aPerformance.playID(),
Optional.of(playFor.apply(aPerformance)),
aPerformance.audience(),
Optional.of(amountFor.apply(aPerformance)),
Optional.of(volumeCreditsFor.apply(aPerformance)));
final var statementData = new StatementData(
invoice .customer(),
invoice.performances().stream().map(enrichPerformance).collect(toList()));
return renderPlainText(statementData);
}
static String renderPlainText(StatementData data) {
Supplier<Integer> totalVolumeCredits = () -> {
var result = 0;
for (Performance perf : data.performances())
result += perf.volumeCredits().get();
return result;
};
var result = "Statement for " + data.customer() + "n";
for(Performance perf : data.performances())
result += " "+ perf.play().get().name()+": "+usd.apply(perf.amount().get()/100)
+ " (" + perf.audience() + " seats)n";
result += "Amount owed is " + usd.apply(totalAmount.get()/100) + "n";
result += "You earned " + totalVolumeCredits.get() + " creditsn";
return result;
}
record Performance(
String playID, Optional<Play> play, int audience,
Optional<Integer> amount, Optional<Integer> volumeCredits
){ Performance(String playID, int audience) {
this(playID, Optional.empty(), audience, Optional.empty(), Optional.empty()); }}