>  기사  >  php教程  >  반복자 패턴을 사용하여 객체를 문자열로 변환

반복자 패턴을 사용하여 객체를 문자열로 변환

高洛峰
高洛峰원래의
2016-12-13 17:46:481431검색

JSOM, XML, Java Bean 및 기타 객체를 조작할 때 먼저 방문자 패턴을 생각할 수 있습니다. 그러나 방문자 패턴을 사용하면 호출 코드에서 콜백을 제어하기가 어렵습니다. 예를 들어 모든 콜백 하위 분기 및 리프 노드에서 분기를 조건부로 건너뛸 수 없습니다. 이 문제를 해결하려면 반복자 패턴을 사용하여 전체 개체를 탐색하고 개발자가 쉽게 읽고 디버그할 수 있는 문자열을 생성할 수 있습니다. 이 반복자는 매우 다재다능하며 XPath를 사용하여 Java 개체를 찾고 StackHunter에서 예외를 기록하는 것과 같은 도구에서 사용했습니다.

API

이 글에서는 주로 StringGenerator와 ObjectIterator라는 두 가지 클래스를 소개합니다.

문자열 생성기

StringGenerator 도구 클래스는 객체를 더 읽기 쉽게 만들기 위해 객체를 문자열로 변환합니다. 이를 사용하여 클래스의 toString 메소드를 구현하거나 객체의 문자열 표현식을 로그 디버깅 코드로 사용할 수 있습니다.

package com.stackhunter.util.tostring.example;
 
import com.stackhunter.example.employee.Department;
import com.stackhunter.example.employee.Employee;
import com.stackhunter.example.employee.Manager;
import com.stackhunter.example.people.Person;
 
import com.stackhunter.util.tostring.StringGenerator;
 
public class StringGeneratorExample {
 
    public static void main(String[] args) {
        Department department = new Department(5775, "Sales")
        .setEmployees(
                new Employee(111, "Bill", "Gates"), 
                new Employee(222, "Howard", "Schultz"), 
                new Manager(333, "Jeff", "Bezos", 75000));
 
        System.out.println(StringGenerator.generate(department));
        System.out.println(StringGenerator.generate(new int[] { 111, 222, 333 }));
        System.out.println(StringGenerator.generate(true));
    }
 
}

StringGenerator.generate()는 출력을 위해 부서, 배열 및 부울 값의 형식을 지정합니다. .

com.stackhunter.example.employee.Department@129719f4
  deptId = 5775
  employeeList = java.util.ArrayList@7037717a
    employeeList[0] = com.stackhunter.example.employee.Employee@17a323c0
      firstName = Bill
      id = 111
      lastName = Gates
    employeeList[1] = com.stackhunter.example.employee.Employee@57801e5f
      firstName = Howard
      id = 222
      lastName = Schultz
    employeeList[2] = com.stackhunter.example.employee.Manager@1c4a1bda
      budget = 75000.0
      firstName = Jeff
      id = 333
      lastName = Bezos
  name = Sales
[I@39df3255
  object[0] = 111
  object[1] = 222
  object[2] = 333
true

객체 반복자

ObjectIterator는 반복자 패턴을 사용하여 객체의 속성을 순회하고 키-값 쌍 형식으로 저장합니다. 객체의 Java Bean, 컬렉션, 배열 및 맵을 반복해야 합니다. ObjectIterator는 또한 객체 간의 순환 참조 처리를 고려합니다.

package com.stackhunter.util.tostring.example;
 
import com.stackhunter.example.employee.Department;
import com.stackhunter.example.employee.Employee;
import com.stackhunter.example.employee.Manager;
import com.stackhunter.util.objectiterator.ObjectIterator;
 
public class ObjectIteratorExample {
 
    public static void main(String[] args) {
        Department department = new Department(5775, "Sales")
        .setEmployees(
                new Employee(111, "Bill", "Gates"), 
                new Employee(222, "Howard", "Schultz"), 
                new Manager(333, "Jeff", "Bezos", 75000));
 
        ObjectIterator iterator = new ObjectIterator("some department", department);
         
        while (iterator.next()) {
            System.out.println(iterator.getName() + "=" + iterator.getValueAsString());
        }
    }
 
}

전체 객체를 순회하여 키-값 쌍 컬렉션을 생성합니다. toString() 대신 getValueAsString() 메서드를 사용하여 출력 형식을 지정합니다. 기본 유형, 래핑된 유형, 문자열, 날짜 및 열거형에는 원래 toString() 구현을 사용하십시오. 다른 유형의 경우 클래스 이름과 해시 값이 출력됩니다.

ObjectIterator.getDepth()는 들여쓰기를 늘리고 출력을 더 읽기 쉽게 만듭니다. 현재 분기를 단축하고 다음 속성으로 이동하려면 next()를 호출하기 전에 nextParent()를 사용하십시오.

some department=com.stackhunter.example.employee.Department@780324ff
deptId=5775
employeeList=java.util.ArrayList@6bd15108
employeeList[0]=com.stackhunter.example.employee.Employee@22a79c31
firstName=Bill
...

Java 객체 반복자의 구체적인 구현

반복자 패턴을 구현하는 첫 번째 단계는 일반 반복자 인터페이스인 IObjectIterator를 만드는 것입니다. 이 인터페이스는 탐색된 객체가 Java Bean, 배열 또는 맵인지 여부에 관계없이 사용할 수 있습니다.

public interface IObjectIterator {
    boolean next();
    String getName();
    Object getValue();
}

이 인터페이스를 사용하면 현재 속성의 이름과 값을 단일 순서로 얻을 수 있습니다.

IObjectIterator를 구현하는 클래스는 특정 유형의 개체를 처리하는 데 사용됩니다. 대부분의 클래스는 getName()을 호출하여 이름 접두사를 반환합니다. ArrayIterator는 요소의 인덱스를 사용합니다: 반환 이름 + "[" + nextIndex + "]";.

반복자 패턴을 사용하여 객체를 문자열로 변환

Property Iterator

PropertyIterator는 아마도 가장 중요한 반복 클래스일 것입니다. 이는 Java Bean 내부 검사를 사용하여 객체 속성을 읽고 이를 일련의 키-값 쌍으로 변환합니다.

public class PropertyIterator implements IObjectIterator {
 
    private final Object object;
    private final PropertyDescriptor[] properties;
    private int nextIndex = -1;
    private PropertyDescriptor currentProperty;
 
    public PropertyIterator(Object object) {
        this.object = object;
        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(object.getClass());
            properties = beanInfo.getPropertyDescriptors();
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }
 
    @Override
    public boolean next() {
        if (nextIndex + 1 >= properties.length) {
            return false;
        }
 
        nextIndex++;
        currentProperty = properties[nextIndex];
        if (currentProperty.getReadMethod() == null || "class".equals(currentProperty.getName())) {
            return next();
        }
        return true;
    }
 
    @Override
    public String getName() {
        if (currentProperty == null) {
            return null;
        }
        return currentProperty.getName();
    }
 
    @Override
    public Object getValue() {
        try {
            if (currentProperty == null) {
                return null;
            }
            return currentProperty.getReadMethod().invoke(object);
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }
 
}

Array Iterator

ArrayIterator는 리플렉션을 통해 배열의 길이를 가져온 다음 각 데이터 요소를 검색합니다. ArrayIterator는 getValue() 메서드에서 반환된 값의 특정 세부 정보에 관심이 없습니다. 일반적으로 PropertyIterator에 전달됩니다.

public class ArrayIterator implements IObjectIterator {
 
    private final String name;
    private final Object array;
    private final int length;
    private int nextIndex = -1;
    private Object currentElement;
 
    public ArrayIterator(String name, Object array) {
        this.name = name;
        this.array = array;
        this.length = Array.getLength(array);
    }
 
    @Override
    public boolean next() {
        if (nextIndex + 1 >= length) {
            return false;
        }
 
        nextIndex++;
        currentElement = Array.get(array, nextIndex);
        return true;
    }
 
    @Override
    public String getName() {
        return name + "[" + nextIndex + "]";
    }
 
    @Override
    public Object getValue() {
        return currentElement;
    }
 
}

集合迭代器

CollectionIterator与ArrayIterator非常相似。使用java.lang.Iterable调用它的Iterable.iterator()方法初始化内部迭代器。

Map迭代器

MapIterator遍历java.util.Map的entry。它并不深入到每个entry的键值对,这个工作由MapEntryIterator类完成。

public class MapIterator implements IObjectIterator {
 
    private final String name;
    private Iterator<?> entryIterator;
    private Map.Entry<?, ?> currentEntry;
    private int nextIndex = -1;
 
    public MapIterator(String name, Map<?, ?> map) {
        this.name = name;
        this.entryIterator = map.entrySet().iterator();
    }
 
    @Override
    public boolean next() {
        if (entryIterator.hasNext()) {
            nextIndex++;
            currentEntry = (Entry<?, ?>) entryIterator.next();
            return true;
        }
        return false;
    }
 
    ...
 
}

Map Entry迭代器

MapEntryIterator处理java.util.Map的单个entry。它只返回两个值:entry的键和值。与ArrayIterator及其他的类似,如果是复杂类型的话,它的结果可能最终传递给PropertyIterator,作为Java bean处理。

根迭代器

RootIterator返回单个元素——初始节点。可以把它想成XML文件的根节点。目的是发起整个遍历过程。

整合

ObjectIterator类作为门面角色(Facade),包装了所有的遍历逻辑。它根据最后一次getValue()的返回值类型决定哪个IObjectIterator的子类需要实例化。当子迭代器在内部创建时它在栈中保存当前迭代器的状态。它也暴露了getChild()和getDepth()方法为调用者展示当前进度。

private IObjectIterator iteratorFor(Object object) {
    try {
        if (object == null) {
            return null;
        }
 
        if (object.getClass().isArray()) {
            return new ArrayIterator(name, object);
        }
 
        if (object instanceof Iterable) {
            return new CollectionIterator(name, (Iterable<?>) object);
        }
 
        if (object instanceof Map) {
            return new MapIterator(name, (Map<?, ?>) object);
        }
 
        if (object instanceof Map.Entry) {
            return new MapEntryIterator(name, (Map.Entry<?, ?>) object);
        }
 
        if (isSingleValued(object)) {
            return null;
        }
 
        return new PropertyIterator(object);
    } catch (RuntimeException e) {
        throw e;
    } catch (Exception e) {
        throw new RuntimeException(e.getMessage(), e);
    }
 
}

字符串生成器的实现

已经看到如何遍历对象中的所有属性。最后的工作就是让输出更加美观,以及增加一些限制条件(比如不能创建一个GB级大小的字符串)。

public static String generate(Object object) {
    String s = "";
 
    ObjectIterator iterator = new ObjectIterator("object", object);
 
    ...
 
    while (iterator.next()) {
        if (s.length() >= MAX_STRING_LENGTH) {
            return s;
        }
 
        if (iterator.getChild() >= MAX_CHILDREN) {
            iterator.nextParent();
            continue;
        }
 
        String valueAsString = iterator.getValueAsString();
 
        s += System.lineSeparator();
        s += indent(iterator.getDepth()) + truncateString(iterator.getName());
        if (valueAsString == null) {
            s += " = null";
        } else {
            s += " = " + truncateString(valueAsString);
        }
    }
 
    return s;
}

代码第21行完成了格式化,增加了缩进和层次结构使显示更美观。

同时增加了一些限制条件:

第9行——限制字符串长度在16k内。

第13行——限制任何父节点下子节点数量小于64.

第21&25行——限制键和值长度在64个字符。

总结

本文介绍了如何使用迭代器模式遍历包含各种复杂属性的对象。关键是将每种类型的迭代委派给各自的类实现。可以在你自己的软件中使用这两个工具。


성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.