Groovyで独自の言語を作成する

命令型プログラミング言語の主な問題は、自然言語との距離が近いことです。

OOPはこの問題を部分的に解決し、データとロジックをオブジェクトのクラスに分類しましたが、それでも理解するのは難しいようです。 ここでの主な問題は、命令型言語がオブジェクト階層およびネストされたメソッド呼び出しで動作するように適合されていないことです。

たとえば、顧客の製品注文を処理するためのクラスの階層があります。
//  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) } 


ジョブ記述自体のビジネスロジックは次のようになります。
 //  - 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) 


Groovyのおかげで、コードは非常にシンプルで読みやすくなりました。 しかし、例は複雑ではありません。 実際には、オブジェクトを管理するための複雑なビジネスロジックを記述するとき、コードはかさばり、読みにくくなります。 コードからのみ制御される特定のAPIがあり、書くのが難しく、読むのが難しいことがわかりました。

Groovyには、必要なアクションをどのように実行するかを簡単に記述する独自の宣言型マークアップ言語を記述することにより、人生を簡素化する機会があります。 上記のビジネスロジックの類似物がマークアップ言語でどのように見えるかを次に示します。
 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) } 

そのようなコードは、コメントをまったく必要としません-読みやすさがあります。

この言語を実装するには、Groovyでビルダーを作成する必要があります。 Groovyには抽象クラスBuilderSupportがあり、これから継承して独自のBuilderを作成する必要があります。 継承されたクラスでは、コード内のマークアップ言語を解析するときにGroovyが自動的に呼び出すいくつかのメソッドをオーバーライドする必要があります。 ビルダークラスは次のようになります。
 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 } } 

このクラスでは、2つの抽象メソッドsetParentとcreateNodeがオーバーライドされます。 setParentは、親ノードがノードに割り当てられ、ロジックで使用されていないときに呼び出されます。 ただし、createNodeでは、各マークアップ要素に対して呼び出されるだけです。 マークアップノードの説明の構文に応じて、4つのオーバーロードされたcreateNodeメソッドの1つが呼び出されます。 私の構文は、要素に常にパラメーターがあることを前提としています。 したがって、必要な機能を目的のメソッドに登録し、他のすべてのcreateNodeメソッドに例外を追加しました。 これにより、メソッド呼び出しを記述するための誤った構文を制御および排除できます。 唯一の例外は、ルートラベル呼び出しに対して行われました。これは、ビルダーがパラメーターなしで起動したときに最初に自動的に作成されます。 コンストラクターでビルダークラスを拡張し、そこに作成された顧客リスト、製品、注文のオブジェクトが転送されます。 ビジネスエンティティを追加するためのクラスメソッドでも説明されています。 複雑なことはありません。コードとそのコメントにすべてが目立って表示されます。

そして、作成したマークアップ言語を使用して結果を確認するための最終的なコードを次に示します。
 //  - 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 

結果:
追加された顧客1234:顧客
追加された製品a100:製品1
追加された製品a200:製品2
顧客Customerからの注文1を追加しました
製品アイテム1とともに注文1の詳細pos 1に追加
製品アイテム2とともに注文1の詳細pos 2に追加

***結果***
顧客:
{inn = 1234、name = client、address = Russia、phone = + 74951002030}
製品:
{記事= a100、名前=アイテム1、価格= 100.0}
{記事= a200、名前=製品2、価格= 200.0}
注文:
{num = 1、customer = Customer、
detail = {pos = 1、product = Product 1、count = 1、sum = 100.0};
{pos = 2、product = Product 2、count = 1、sum = 200.0}}

すべてが動作します:)

要約すると、ビルダーの範囲は広いと言えます。 たとえば、現在、オープンソースプロジェクトGETL(GroovyベースのETL)のデータ変換を記述するための言語を開発しています。 このビルダーを使用すると、コードでSQLクエリを収集したり、階層形式のネイティブ形式で情報を表示したりできる構文を簡単に開発できます。 はい、そして通常のXML / JSONマーカーは秘密を構成しないと思います。 マークアップ言語は、Groovyプログラムコードで使用できるだけでなく、オブジェクトとアクションを別々のファイルに取り出すためのブロックとしても使用できます。 記述ブロックは、実行時にファイルから直接読み取り、EVALメソッドを使用して実行できます。 ブロックは十分に形式化されているため、一般的なユーザーがビジネスロジックを開発するための独自のGUIを簡単に作成できます。

多くの例があります。 しかし、最も重要なことは、忘れないでください-上記のすべては、Javaで問題なく動作します! Groovyでは、Javaクラスやメソッドを独自のマークアップ言語と結び付けて、ビジネスロジックを作成し、Javaアプリケーションでさらに使用することは誰も気にしません。 これらの機能は、Javaで魔法のGroovyを使い始める価値があります:)

ここからクラスによって記事で使用された全文をダウンロードしてください

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


All Articles