📜 ⬆️ ⬇️

Create your own groovy language

The main problem of imperative programming languages ​​is their low proximity to natural languages.

OOP has solved this problem in parts, ordering the data and logic according to object classes, but still it looks difficult to understand. The main problem here is that imperative languages ​​are not adapted for working with object hierarchies and nested method calls.

For example, I have a hierarchy of customer service order classes:
//  class Customer { int inn String name String address String phone } //  class Customers { Customer findByInn(inn) void add(Customer customer) } //  class Product { String article String name double price } //  class Products { Product findByArticle(article) void add(Product product) } //  class Order { int num Customer customer List<OrderDetail> details = [] OrderDetail findByPos(pos) void add(OrderDetail detail) } //   class OrderDetail { int pos Product product def count = 1 def getSum() { count * product.price } } //  class Orders { Order findByNum(num) void add(Order order) } 

')
The business logic of the job description will look like this:
 //  - def customers = new Customers() def products = new Products() def orders = new Orders() //   customers.add(new Customer(inn: 1234, name: "", address: "", phone: "+74951002030")) //   products.add(new Product(article: "a100", name: " 1", price: 100.00)) products.add(new Product(article: "a200", name: " 2", price: 200.00)) //   def order = new Order(num: 1, customer: customers.findByInn(1234)) order.add(new OrderDetail(pos: 1, product: products.findByArticle("a100"), count: 1)) order.add(new OrderDetail(pos: 2, product: products.findByArticle("a200"), count: 1)) orders.add(order) 


Thanks to the elegance of Groovy, the code is quite simple and readable. But the example is not complicated. In real life, when writing complex business objects management logic, the code will look cumbersome and hard to read. It turns out, I have a certain API which is controlled only from the code, it is difficult to write and not easily read.

Groovy has the ability to simplify your life by writing your own declarative markup language to easily describe how to perform the necessary actions. Here is how an analogue of the above described business logic will look like in a markup language:
 AddCustomer(inn: 1234, name: "", address: "", phone: "+74951002030") AddProduct(article: "a100", name: " 1", price: 100.00) AddProduct(article: "a200", name: " 2", price: 200.00) AddOrder(num: 1, customer: 1234) { Detail(pos: 1, product: "a100", count: 1) Detail(pos: 2, product: "a200", count: 1) } 

Such code does not need any comments at all - it has high readability.

To implement this language, you need to write a Groovy builder. Groovy has an abstract BuilderSupport class, from which you need to inherit to create your builder. In an inherited class, you will need to override a number of methods that Groovy will automatically invoke when parsing a markup language in code. Here’s what the builder class will look like:
 public class MyBuilder extends BuilderSupport { public Customers customers public Products products public Orders orders //     protected void setParent(Object parent, Object child) { } //     protected Object createNode(Object name) { if (name != "call") throw new Exception("Node required parameters") new Node(null, name); } //        protected Object createNode(Object name, Object value) { throw new Exception("Node required parameters") } //     protected Object createNode(Object name, Map attributes) { //     Node parent = getCurrent() def result //    switch (name) { case "AddCustomer": result = addCustomer(attributes) break case "AddProduct": result = addProduct(attributes) break case "AddOrder": result = addOrder(attributes) break case "Detail": if (parent == null || parent.name() != "AddOrder") throw new Exception( "Detail must be specified with only AddOrder") result = addOrderDetail(parent.value(), attributes) break defailt: throw new Exception("Unknown node ${name}") } new Node(null, name, attributes, result); } //          protected Object createNode(Object name, Map attributes, Object value) { throw new Exception("Node ${name} can not support objects") } //   def addCustomer(Map params) { def customer = new Customer(inn: params.inn, name: params.name, address: params.address, phone: params.phone) customers.add(customer) println "Added customer ${customer.inn}: ${customer.name}" customer } //   def addProduct(Map params) { def product = new Product(article: params.article, name: params.name, price: params.price) products.add(product) println "Added product ${product.article}: ${product.name}" product } //   def addOrder(Map params) { def order = new Order(num: 1, customer: customers.findByInn(params.customer)) orders.add(order) println "Added order ${order.num} from customer ${order.customer.name}" order } //    def addOrderDetail(Order order, Map params) { def count = params.count?:1 def detail = new OrderDetail(pos: params.pos, product: products.findByArticle(params.product), count: count) order.add(detail) println "Added into order ${order.num} detail pos ${detail.pos} " + "with product ${detail.product.name}" detail } } 

In this class two abstract methods setParent and createNode are overlapped. setParent is called when assigning a child node to a parent and is not used in my logic. But in createNode it is just called on each markup element. Depending on the syntax of the markup node description, one of the four overloaded createNode methods is called. My syntax assumes that elements always have parameters. Therefore, I have registered the necessary functionality in the required method and added exceptions to all other createNode methods. This will allow you to check and eliminate the incorrect syntax of the description of the call method. The only exception was made for the call call mark, which is automatically created first when launching the builder without parameters. I have expanded the builder class with a designer to which the created objects of customer lists, products and orders are transferred. I also described in the class methods for adding business entities. Nothing complicated - everything is perfectly visible in the code and in the comments in it.

And here is the final code to use the created markup language with checking the results:
 //  - def customers = new Customers() def products = new Products() def orders = new Orders() //    def myApi = new MyBuilder(customers: customers, products: products, orders: orders) //   myApi { AddCustomer(inn: 1234, name: "", address: "", phone: "+74951002030") AddProduct(article: "a100", name: " 1", price: 100.00) AddProduct(article: "a200", name: " 2", price: 200.00) AddOrder(num: 1, customer: 1234) { Detail(pos: 1, product: "a100", count: 1) Detail(pos: 2, product: "a200", count: 1) } } //   println "\n*** Result ***" println "Customers:" println customers println "Products:" println products println "Orders:" println orders 

Result:
Added customer 1234: Customer
Added product a100: Item 1
Added product a200: Item 2
Added order 1 from customer Customer
Added into order 1 detail pos 1 with product Item 1
Added into order 1 detail pos 2 with product Item 2

*** Result ***
Customers:
{inn = 1234, name = Client, address = Russia, phone = + 74951002030}
Products:
{article = a100, name = Item 1, price = 100.0}
{article = a200, name = Item 2, price = 200.0}
Orders:
{num = 1, customer = Customer,
detail = {pos = 1, product = Item 1, count = 1, sum = 100.0};
{pos = 2, product = Item 2, count = 1, sum = 200.0}}

Everything is working :)

Summarizing, we can say that the scope of the builders is large. For example, I am currently developing a data transformation description language based on it for my open source project GETL (Groovy-based ETL). With the help of the builder, you can easily develop a syntax that allows you to collect SQL queries in code or display information in a hierarchical format of its own. Yes, and I think XML / JSON markers are now no mystery. The markup language can be used not only in the code of Groovy programs, but also as blocks for describing objects and actions that are rendered into separate files. Description blocks can be read from files directly at runtime and executed using the EVAL method. Since the blocks are well formalized, it is easy for them to write your own business logic GUI for regular users.

Examples are many. But most importantly, do not forget - all of the above is wonderful without any effort running on Java! In Groovy, no one bothers to tie up any Java classes and methods with their own markup language, in which to write business logic, which is further used in Java applications. These features are worth starting to use Magic Groovy in Java :)

Download the full text used in the article class can be here .

Source: https://habr.com/ru/post/174683/


All Articles